From e76a5b0bfe6ddfdf409d6d9a1aaf088eb559f118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Igor=20Pe=C4=8Dovnik?= Date: Wed, 5 Jan 2022 17:55:10 +0100 Subject: [PATCH] Improve fist login UX (#3375) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve 1st login script * Update packages/bsp/common/usr/lib/armbian/armbian-firstlogin Co-authored-by: Manuel Rüger * Address accessibility issue Co-authored-by: Manuel Rüger --- lib/distributions.sh | 3 + .../common/usr/lib/armbian/armbian-firstlogin | 247 ++++++++++-------- 2 files changed, 146 insertions(+), 104 deletions(-) diff --git a/lib/distributions.sh b/lib/distributions.sh index 01567b24ca18..82d7923e74a5 100644 --- a/lib/distributions.sh +++ b/lib/distributions.sh @@ -581,6 +581,9 @@ FAMILY_TWEAKS # build logo in any case boot_logo + # disable MOTD for first boot - we want as clean 1st run as possible + chmod -x "${SDCARD}"/etc/update-motd.d/* + } install_rclocal() diff --git a/packages/bsp/common/usr/lib/armbian/armbian-firstlogin b/packages/bsp/common/usr/lib/armbian/armbian-firstlogin index 1a52ace2c9e1..e38d95acf24d 100755 --- a/packages/bsp/common/usr/lib/armbian/armbian-firstlogin +++ b/packages/bsp/common/usr/lib/armbian/armbian-firstlogin @@ -102,75 +102,98 @@ set_timezone_and_locales() # Grab this machine's public IP address PUBLIC_IP=`curl --max-time 5 -s https://ipinfo.io/ip` - if [ $? -eq 0 ]; then - - # Call the geolocation API and capture the output - RES=$( - curl --max-time 5 -s http://ipwhois.app/json/${PUBLIC_IP} | \ - jq '.timezone, .country, .country_code' | \ - while read -r TIMEZONE; do - read -r COUNTRY - echo "${TIMEZONE},${COUNTRY},${COUNTRYCODE}" | tr --delete \" - done - ) - - TZDATA=$(echo ${RES} | cut -d"," -f1) - STATE=$(echo ${RES} | cut -d"," -f2) - CCODE=$(echo ${RES} | cut -d"," -f3 | xargs) - LOCALES=$(grep territory /usr/share/i18n/locales/* | grep _"$CCODE" | cut -d ":" -f 1 | cut -d "/" -f 6 | \ - xargs -I{} grep {} /usr/share/i18n/SUPPORTED | grep "\.UTF-8" | cut -d " " -f 1) - # UTF8 is not present everywhere so check again in case it returns empty value - [[ -z "$LOCALES" ]] && LOCALES=$(grep territory /usr/share/i18n/locales/* | grep _"$CCODE" | cut -d ":" -f 1 | cut -d "/" -f 6 | \ - xargs -I{} grep {} /usr/share/i18n/SUPPORTED | cut -d " " -f 1) - echo -e "Detected timezone: \x1B[92m$TZDATA\x1B[0m" - echo "" - read -n1 -s -r -p "Set user language based on your location? [Y/n] " response - echo "" - # change it only if we have a match and if we agree - if [[ -n "$LOCALES" && "${response}" =~ ^(Y|y|"")$ ]]; then - - options=(`echo ${LOCALES}`); - - # when having more locales, prompt for choosing one - if [[ "${#options[@]}" -gt 1 ]]; then - - options+=("Skip generating locales") - echo -e "\nAt your location, more locales are possible:\n" - PS3='Please enter your choice:' - select opt in "${options[@]}" - do - if [[ " ${options[@]} " =~ " ${opt} " ]]; then - LOCALES=${opt} - break - fi - done + # Check if we have wireless adaptor + WIFI_DEVICE=$(LC_ALL=C nmcli dev status | grep " wifi " 2>/dev/null) + + if [ -z "$PUBLIC_IP" ]; then + + # ask for connecting to wireless if wifi device is found + if [[ -n "$WIFI_DEVICE" ]]; then + echo -e "Internet connection was \x1B[91mnot detected\x1B[0m." + echo "" + read -n1 -s -r -p "Connect via wireless? [Y/n] " response + echo "" + if [[ "${response}" =~ ^(Y|y|"")$ ]]; then + nmtui-connect fi + echo "" + fi + fi - if [[ "${LOCALES}" != *Skip* ]]; then + # Grab IP once again if not found + [[ -z "$PUBLIC_IP" && -n "$WIFI_DEVICE" ]] && PUBLIC_IP=`curl --max-time 5 -s https://ipinfo.io/ip` - # reconfigure tzdata - timedatectl set-timezone "${TZDATA}" - dpkg-reconfigure --frontend=noninteractive tzdata > /dev/null 2>&1 + # Call the geolocation API and capture the output + RES=$( + curl --max-time 5 -s http://ipwhois.app/json/${PUBLIC_IP} | \ + jq '.timezone, .country, .country_code' | \ + while read -r TIMEZONE; do + read -r COUNTRY + echo "${TIMEZONE},${COUNTRY},${COUNTRYCODE}" | tr --delete \" + done + ) + + TZDATA=$(echo ${RES} | cut -d"," -f1) + STATE=$(echo ${RES} | cut -d"," -f2) + CCODE=$(echo ${RES} | cut -d"," -f3 | xargs) + echo -e "Detected timezone: \x1B[92m$TZDATA\x1B[0m" + echo "" + read -n1 -s -r -p "Set user language based on your location? [Y/n] " response + echo "" + # change it only if we have a match and if we agree + if [[ "${response}" =~ ^(N|n)$ ]]; then + unset CCODE TZDATA + fi - # generate locales - echo "" - sed -i 's/# '"${LOCALES}"'/'"${LOCALES}"'/' /etc/locale.gen - echo -e "Generating locales: \x1B[92m${LOCALES}\x1B[0m" - locale-gen $LOCALES > /dev/null 2>&1 + LOCALES=$(grep territory /usr/share/i18n/locales/* | grep _"$CCODE" | cut -d ":" -f 1 | cut -d "/" -f 6 | \ + xargs -I{} grep {} /usr/share/i18n/SUPPORTED | grep "\.UTF-8" | cut -d " " -f 1) + # UTF8 is not present everywhere so check again in case it returns empty value + [[ -z "$LOCALES" ]] && LOCALES=$(grep territory /usr/share/i18n/locales/* | grep _"$CCODE" | cut -d ":" -f 1 | cut -d "/" -f 6 | \ + xargs -I{} grep {} /usr/share/i18n/SUPPORTED | cut -d " " -f 1) - # setting detected locales only for user - echo "export LC_ALL=$LOCALES" >> /home/$RealUserName/.bashrc - echo "export LANG=$LOCALES" >> /home/$RealUserName/.bashrc - echo "export LANGUAGE=$LOCALES" >> /home/$RealUserName/.bashrc - echo "export LC_ALL=$LOCALES" >> /home/$RealUserName/.xsessionrc - echo "export LANG=$LOCALES" >> /home/$RealUserName/.xsessionrc - echo "export LANGUAGE=$LOCALES" >> /home/$RealUserName/.xsessionrc + options=(`echo ${LOCALES}`); - fi + # when having more locales, prompt for choosing one + if [[ "${#options[@]}" -gt 1 ]]; then + + options+=("Skip generating locales") + echo -e "\nAt your location, more locales are possible:\n" + PS3='Please enter your choice:' + select opt in "${options[@]}" + do + if [[ " ${options[@]} " =~ " ${opt} " ]]; then + LOCALES=${opt} + break + fi + done fi - fi + if [[ "${LOCALES}" != *Skip* ]]; then + + # if TZDATA was not detected, we need to select one + if [[ -z ${TZDATA} ]]; then + TZDATA=$(tzselect | tail -1) + fi + + timedatectl set-timezone "${TZDATA}" + dpkg-reconfigure --frontend=noninteractive tzdata > /dev/null 2>&1 + + # generate locales + echo "" + sed -i 's/# '"${LOCALES}"'/'"${LOCALES}"'/' /etc/locale.gen + echo -e "Generating locales: \x1B[92m${LOCALES}\x1B[0m" + locale-gen "${LOCALES}" > /dev/null 2>&1 + + # setting detected locales only for user + echo "export LC_ALL=$LOCALES" >> /home/"$RealUserName"/.bashrc + echo "export LANG=$LOCALES" >> /home/"$RealUserName"/.bashrc + echo "export LANGUAGE=$LOCALES" >> /home/"$RealUserName"/.bashrc + echo "export LC_ALL=$LOCALES" >> /home/"$RealUserName"/.xsessionrc + echo "export LANG=$LOCALES" >> /home/"$RealUserName"/.xsessionrc + echo "export LANGUAGE=$LOCALES" >> /home/"$RealUserName"/.xsessionrc + + fi } @@ -204,23 +227,29 @@ add_profile_sync_settings() add_user() { read -t 0 temp - + REPEATS=3 while [ -f "/root/.not_logged_in_yet" ]; do - echo -e "\nPlease provide a username (eg. your forename): \c" + echo -e "\nPlease provide a username (eg. your first name): \c" read -e username + if ! grep '^[a-zA-Z]*$' <<< $username > /dev/null ; then + echo -e "\n\x1B[91mError\x1B[0m: illegal characters in username" + return + fi + + RealUserName="$(echo "$username" | tr '[:upper:]' '[:lower:]' | tr -d -c '[:alnum:]')" [ -z "$RealUserName" ] && return if ! id "$RealUserName" >/dev/null 2>&1; then break; else echo -e "Username \e[0;31m$RealUserName\x1B[0m already exists on the system."; fi done while [ -f "/root/.not_logged_in_yet" ]; do - read_password "Create" - first_input=$password + read_password "Create user ($username)" + first_input="$password" echo "" - read_password "Repeat" - second_input=$password + read_password "Repeat user ($username)" + second_input="$password" echo "" - if [[ $first_input == $second_input ]]; then + if [[ "$first_input" == "$second_input" ]]; then result="$(cracklib-check <<<"$password")" okay="$(awk -F': ' '{ print $2}' <<<"$result")" if [[ "$okay" == "OK" ]]; then @@ -228,34 +257,37 @@ add_user() read -e -p "Please provide your real name: " -i "${RealUserName^}" RealName adduser --quiet --disabled-password --home /home/"$RealUserName" --gecos "$RealName" "$RealUserName" - (echo $first_input;echo $second_input;) | passwd "$RealUserName" >/dev/null 2>&1 + (echo "$first_input";echo "$second_input";) | passwd "$RealUserName" >/dev/null 2>&1 for additionalgroup in sudo netdev audio video disk tty users games dialout plugdev input bluetooth systemd-journal ssh; do - usermod -aG ${additionalgroup} ${RealUserName} 2>/dev/null + usermod -aG "${additionalgroup}" "${RealUserName}" 2>/dev/null done # fix for gksu in Xenial - touch /home/$RealUserName/.Xauthority - chown $RealUserName:$RealUserName /home/$RealUserName/.Xauthority + touch /home/"$RealUserName"/.Xauthority + chown "$RealUserName":"$RealUserName" /home/"$RealUserName"/.Xauthority RealName="$(awk -F":" "/^${RealUserName}:/ {print \$5}" /dev/null 2>&1 if [ $? -eq 0 ]; then echo -e "${RealUserName} ALL=(ALL) NOPASSWD: /usr/bin/psd-overlay-helper" >> /etc/sudoers - touch /home/${RealUserName}/.activate_psd - chown $RealUserName:$RealUserName /home/${RealUserName}/.activate_psd + touch /home/"${RealUserName}"/.activate_psd + chown "$RealUserName":"$RealUserName" /home/"${RealUserName}"/.activate_psd fi break else - echo -e "Rejected - \e[0;31m$okay.\x1B[0m Try again." + echo -e "Rejected - \e[0;31m$okay.\x1B[0m Try again [${REPEATS}]." + REPEATS=$((REPEATS - 1)) fi elif [[ -n $password ]]; then - echo -e "Rejected - \e[0;31mpasswords do not match.\x1B[0m Try again." + echo -e "Rejected - \e[0;31mpasswords do not match.\x1B[0m Try again [${REPEATS}]." + REPEATS=$((REPEATS - 1)) fi + [[ "$REPEATS" -eq 0 ]] && exit done } @@ -270,58 +302,65 @@ if [[ -f /root/.not_logged_in_yet && -n $(tty) ]]; then desktop_lightdm=$(dpkg-query -W -f='${db:Status-Abbrev}\n' lightdm 2>/dev/null) desktop_gdm3=$(dpkg-query -W -f='${db:Status-Abbrev}\n' gdm3 2>/dev/null) - echo -e "\nWaiting for system to finish booting ...\n" + echo -e "\nWaiting for system to finish booting ..." systemctl is-system-running --wait >/dev/null - if [ "$IMAGE_TYPE" != "nightly" ]; then - if [ "$BRANCH" == "dev" ]; then - echo -e "\nYou are using an Armbian preview build !!!" - echo -e "\nThis image is provided \e[0;31mAS IS\x1B[0m with \e[0;31mNO WARRANTY\x1B[0m and \e[0;31mNO END USER SUPPORT!\x1B[0m.\n" - elif [ "$DISTRIBUTION_STATUS" != "supported" ]; then - echo -e "\nYou are using an Armbian with unsupported ($DISTRIBUTION_CODENAME) userspace !!!" - echo -e "\nThis image is provided \e[0;31mAS IS\x1B[0m with \e[0;31mNO WARRANTY\x1B[0m and \e[0;31mNO END USER SUPPORT!\x1B[0m.\n" - fi - else - echo -e "\nYou are using an Armbian nightly build meant only for developers to provide" - echo -e "constructive feedback to improve build system, OS settings or user experience." - echo -e "If this does not apply to you, \e[0;31mSTOP NOW!\x1B[0m. Especially don't use this image for" - echo -e "daily work since things might not work as expected or at all and may break" - echo -e "anytime with next update. \e[0;31mYOU HAVE BEEN WARNED!\x1B[0m" - echo -e "\nThis image is provided \e[0;31mAS IS\x1B[0m with \e[0;31mNO WARRANTY\x1B[0m and \e[0;31mNO END USER SUPPORT!\x1B[0m.\n" - fi - - echo -e "New to Armbian? Documentation: \e[1m\e[39mhttps://docs.armbian.com\x1B[0m Support: \e[1m\e[39mhttps://forum.armbian.com\x1B[0m\n" + clear + echo -e "Welcome to \e[1m\e[97mARMBIAN\x1B[0m! \n" + echo -e "Documentation: \e[1m\e[92mhttps://docs.armbian.com\x1B[0m | Community: \e[1m\e[92mhttps://forum.armbian.com\x1B[0m\n" trap '' 2 + REPEATS=3 while [ -f "/root/.not_logged_in_yet" ]; do - read_password "New root" + read_password "Create root" # only allow one login. Once you enter root password, kill others. loginfrom=$(who am i | awk '{print $2}') who -la | grep root | grep -v "$loginfrom" | awk '{print $7}' | xargs --no-run-if-empty kill -9 - first_input=$password + first_input="$password" echo "" - read_password "Repeat" - second_input=$password + read_password "Repeat root" + second_input="$password" echo "" - if [[ $first_input == $second_input ]]; then + if [[ "$first_input" == "$second_input" ]]; then result="$(cracklib-check <<<"$password")" okay="$(awk -F': ' '{ print $2}' <<<"$result")" if [[ "$okay" == "OK" ]]; then - (echo $first_input;echo $second_input;) | passwd root >/dev/null 2>&1 + (echo "$first_input";echo "$second_input";) | passwd root >/dev/null 2>&1 break else - echo -e "Rejected - \e[0;31m$okay.\x1B[0m Try again." + echo -e "Rejected - \e[0;31m$okay.\x1B[0m Try again [${REPEATS}]." + REPEATS=$((REPEATS - 1)) fi elif [[ -n $password ]]; then - echo -e "Rejected - \e[0;31mpasswords do not match.\x1B[0m Try again." + echo -e "Rejected - \e[0;31mpasswords do not match.\x1B[0m Try again [${REPEATS}]." + REPEATS=$((REPEATS - 1)) fi + [[ "$REPEATS" -eq 0 ]] && exit done trap - INT TERM EXIT + # display support status + if [ "$IMAGE_TYPE" != "nightly" ]; then + if [[ "$BRANCH" == "edge" ]]; then + echo -e "\nSupport status: \e[0;31mno support\x1B[0m (edge kernel branch)" + elif [[ "$DISTRIBUTION_STATUS" != "supported" ]]; then + echo -e "\nSupport status: \e[0;31mno support\x1B[0m (unsupported userspace)" + fi + else + + echo -e "\e[0;31m\nWARNING!\x1B[0m\n\nYou are using an \e[0;31mautomated build\x1B[0m meant only for developers to provide" + echo -e "constructive feedback to improve build system, OS settings or UX.\n" + + echo -e "If this does not apply to you, \e[0;31mSTOP NOW!\x1B[0m Especially don't use this " + echo -e "image for production since things might not work as expected or at " + echo -e "all. They may break anytime with next update." + + fi + # ask user to select shell trap '' 2 set_shell