mirror of
https://github.com/armbian/build.git
synced 2026-09-08 23:59:54 +08:00
The xmonad package ships two sessions: xmonad.desktop runs the window manager bare, and gnome-flashback-xmonad.desktop runs it inside a GNOME Flashback session, which is the one that gets a session dbus, a polkit agent and session management. Autologin hardcoded the bare one, so the flashback session was never selected even where it was usable. Pick it when it can actually start, and keep the old behaviour otherwise. Its TryExec is gnome-flashback, from gnome-session-flashback, which is not installed on every image, so both the .desktop and the binary are checked. Images without it are unaffected. Signed-off-by: Igor Pecovnik <igor@armbian.com>
1214 lines
42 KiB
Bash
Executable File
1214 lines
42 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Copyright (c) Authors: https://www.armbian.com/authors
|
|
#
|
|
# This file is licensed under the terms of the GNU General Public
|
|
# License version 2. This program is licensed "as is" without any
|
|
# warranty of any kind, whether express or implied.
|
|
|
|
# read distribution status
|
|
# shellcheck source=/dev/null
|
|
[[ -f /etc/lsb-release ]] && . /etc/lsb-release
|
|
[[ -f /etc/os-release ]] && . /etc/os-release
|
|
[[ -z "$DISTRIB_CODENAME" ]] && DISTRIB_CODENAME="${VERSION_CODENAME}"
|
|
[[ -n "$DISTRIB_CODENAME" && -f /etc/armbian-distribution-status ]] && DISTRIBUTION_STATUS=$(grep "^$DISTRIB_CODENAME" /etc/armbian-distribution-status | cut -d"=" -f2 | cut -d";" -f1)
|
|
|
|
. /etc/armbian-release
|
|
. /usr/lib/armbian/armbian-common
|
|
|
|
# Cleanup gate: EXIT trap removes the marker only after FIRSTLOGIN_SUCCESS=1,
|
|
# so power-loss or mid-run failure reruns firstlogin on next boot.
|
|
FIRSTLOGIN_SUCCESS=0
|
|
_firstlogin_cleanup() {
|
|
if [ "$FIRSTLOGIN_SUCCESS" = "1" ]; then
|
|
rm -f /root/.not_logged_in_yet
|
|
sync -f /root 2>/dev/null || sync
|
|
fi
|
|
}
|
|
trap _firstlogin_cleanup EXIT
|
|
# DM takeover (gdm/lightdm) sends SIGHUP to the console's foreground process
|
|
# while bash is mid-command. An untrapped SIGHUP kills bash mid-flight and its
|
|
# EXIT trap gets cut short: builtins still run, but external commands (rm) do
|
|
# not survive the dying shell. Handle the signal in normal execution context
|
|
# instead — cleanup runs fully, then exit.
|
|
trap '_firstlogin_cleanup; exit' HUP INT TERM
|
|
|
|
check_abort() {
|
|
|
|
echo -e "\nDisabling user account creation procedure\n"
|
|
FIRSTLOGIN_SUCCESS=1
|
|
if [[ ${USER_SHELL} == zsh ]]; then
|
|
printf "\nYou selected \e[0;91mZSH\x1B[0m as your default shell. If you want to use it right away, please logout and login! \n\n"
|
|
fi
|
|
trap - INT
|
|
exit 0
|
|
|
|
}
|
|
|
|
mask2cidr() {
|
|
nbits=0
|
|
IFS=.
|
|
for dec in $1 ; do
|
|
case $dec in
|
|
255) let nbits+=8;;
|
|
254) let nbits+=7;;
|
|
252) let nbits+=6;;
|
|
248) let nbits+=5;;
|
|
240) let nbits+=4;;
|
|
224) let nbits+=3;;
|
|
192) let nbits+=2;;
|
|
128) let nbits+=1;;
|
|
0);;
|
|
*) echo "Error: $dec is not recognised"; exit 1
|
|
esac
|
|
done
|
|
echo "$nbits"
|
|
}
|
|
|
|
createYAML() {
|
|
local YAML="network:\n"
|
|
YAML+=" $DEVTYPE:\n"
|
|
YAML+=" $DEVICE_NAME:\n"
|
|
if [[ "${PRESET_NET_USE_STATIC}" == 1 ]]; then
|
|
YAML+=" addresses:\n"
|
|
YAML+=" - $PRESET_NET_STATIC_IP/$PRESET_NET_STATIC_MASK\n"
|
|
if [[ -n "${PRESET_NET_STATIC_GATEWAY}" ]]; then YAML+=" routes:\n";fi
|
|
if [[ -n "${PRESET_NET_STATIC_GATEWAY}" ]]; then YAML+=" - to: default\n";fi
|
|
if [[ -n "${PRESET_NET_STATIC_GATEWAY}" ]]; then YAML+=" via: ${PRESET_NET_STATIC_GATEWAY}\n";fi
|
|
if [[ -n "${PRESET_NET_STATIC_DNS}" ]]; then YAML+=" nameservers:\n"; fi
|
|
if [[ -n "${PRESET_NET_STATIC_DNS}" ]]; then YAML+=" addresses: [$PRESET_NET_STATIC_DNS]\n"; fi
|
|
else
|
|
YAML+=" dhcp4: yes\n"
|
|
YAML+=" dhcp6: yes\n"
|
|
fi
|
|
if [[ "${PRESET_NET_WIFI_ENABLED}" == 1 ]]; then
|
|
if [[ -n "${PRESET_NET_WIFI_COUNTRYCODE}" ]]; then YAML+=" regulatory-domain: $PRESET_NET_WIFI_COUNTRYCODE\n"; fi
|
|
if [[ -n "${PRESET_NET_WIFI_SSID}" ]]; then YAML+=" access-points:\n"; fi
|
|
if [[ -n "${PRESET_NET_WIFI_SSID}" ]]; then YAML+=" \"$PRESET_NET_WIFI_SSID\":\n"; fi
|
|
if [[ -n "${PRESET_NET_WIFI_KEY}" ]]; then YAML+=" password: \"$PRESET_NET_WIFI_KEY\"\n" ; fi
|
|
fi
|
|
printf "%s" "$YAML"
|
|
}
|
|
|
|
do_firstrun_automated_network_configuration()
|
|
{
|
|
#-----------------------------------------------------------------------------
|
|
#Config FP
|
|
local fp_config='/root/.not_logged_in_yet'
|
|
|
|
#-----------------------------------------------------------------------------
|
|
#Grab user requested settings
|
|
if [[ -f $fp_config ]]; then
|
|
|
|
# Convert line endings to Unix from Dos
|
|
sed -i $'s/\r$//' "$fp_config"
|
|
|
|
# check syntax
|
|
bash -n "$fp_config" || return
|
|
|
|
# Load vars directly from file
|
|
source "$fp_config"
|
|
|
|
# Obtain backward configuration compatibility
|
|
PRESET_NET_STATIC_DNS=${PRESET_NET_STATIC_DNS// /,}
|
|
PRESET_NET_STATIC_MASK=$(mask2cidr $PRESET_NET_STATIC_MASK)
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# Set Network
|
|
if [[ $PRESET_NET_CHANGE_DEFAULTS == 1 ]]; then
|
|
|
|
# - Get name of 1st available ethernet and wifi adapter
|
|
eth_index="$(ip link | awk -F: '$0 !~ "lo|vir|wl|^[^0-9]"{print $2;getline}' | sed 's/^[ \t]*//' | grep "^e" | head -1)"
|
|
wlan_index="$(iw dev | awk '$1=="Interface"{print $2}' | head -n 1)"
|
|
|
|
local CONFIG_NAME="-dhcp"
|
|
# for static IP we only append settings
|
|
if [[ $PRESET_NET_USE_STATIC == 1 ]]; then
|
|
local CONFIG_NAME="-static"
|
|
fi
|
|
|
|
# at least one device has to exits
|
|
if [[ -n $eth_index || -n $wlan_index ]]; then
|
|
|
|
# - Wifi enable
|
|
if [[ $PRESET_NET_WIFI_ENABLED == 1 ]]; then
|
|
|
|
DEVICE_NAME=${wlan_index}
|
|
DEVTYPE=wifis
|
|
local _f="/etc/netplan/30-${DEVTYPE}${CONFIG_NAME}.yaml"
|
|
if echo -e "$(createYAML)" > "${_f}.tmp"; then
|
|
atomic_write "${_f}.tmp" "${_f}" 600 || return 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
return 1
|
|
fi
|
|
PRESET_NET_ETHERNET_ENABLED=0
|
|
netplan apply > /dev/null 2>&1
|
|
|
|
# - Ethernet enable
|
|
elif [[ $PRESET_NET_ETHERNET_ENABLED == 1 ]]; then
|
|
|
|
# remove dhcp config
|
|
rm -f /etc/netplan/10-dhcp-all-interfaces.yaml
|
|
|
|
DEVICE_NAME=${eth_index}
|
|
DEVTYPE=ethernets
|
|
local _f="/etc/netplan/30-${DEVTYPE}${CONFIG_NAME}.yaml"
|
|
if echo -e "$(createYAML)" > "${_f}.tmp"; then
|
|
atomic_write "${_f}.tmp" "${_f}" 600 || return 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
return 1
|
|
fi
|
|
PRESET_NET_WIFI_ENABLED=0
|
|
netplan apply > /dev/null 2>&1
|
|
fi
|
|
|
|
fi
|
|
fi
|
|
fi
|
|
} #do_firstrun_automated_network_configuration
|
|
|
|
# Try to retrieve the local IP address to display
|
|
get_local_ip_addr() {
|
|
# How many seconds to wait at maximum to find out the local IP address
|
|
local ip_wait_seconds_counter=6
|
|
local local_device_ip=""
|
|
local retry_message=""
|
|
|
|
while [[ -z "$local_device_ip" ]] && [ ${ip_wait_seconds_counter} -ge 0 ]; do
|
|
local_device_ip=$(ip -4 addr show scope global | grep inet | awk '{print $2}' | cut -d/ -f1 | awk '{$1=$1}1' FS='\n' OFS=',' RS=)
|
|
|
|
# Set retry message if some retries are left, but no IP address has been found yet
|
|
if [[ -z "$local_device_ip" ]] && [[ $ip_wait_seconds_counter -gt 0 ]]; then
|
|
retry_message="\e[1m\e[97mWaiting for local connection!\x1B[0m Retrying... (${ip_wait_seconds_counter})"
|
|
# Empty retry message if IP address has been found
|
|
elif [[ -n "$local_device_ip" ]]; then
|
|
retry_message=""
|
|
# Set timeout message if no retries are left and no IP address has been found
|
|
else
|
|
retry_message="\e[1m\e[31mNetwork connection timeout!\x1B[0m"
|
|
fi
|
|
|
|
# Display the message
|
|
echo -e "\e[1A\e[KIP address: \x1B[92m${local_device_ip}\x1B[0m ${retry_message}"
|
|
|
|
# Wait for 1 second if the IP has not yet been found
|
|
if [[ -z "$local_device_ip" ]]; then
|
|
sleep 1
|
|
fi
|
|
|
|
ip_wait_seconds_counter=$((ip_wait_seconds_counter - 1))
|
|
done
|
|
}
|
|
|
|
read_password() {
|
|
|
|
unset password
|
|
unset charcount
|
|
prompt="$1 password: "
|
|
|
|
stty -echo
|
|
|
|
charcount=0
|
|
while IFS= read -p "$prompt" -r -s -n 1 char; do
|
|
if [[ $char == $'\0' ]]; then
|
|
break
|
|
fi
|
|
|
|
# Handle backspace
|
|
if [[ $char == $'\177' ]]; then
|
|
if [ $charcount -gt 0 ]; then
|
|
charcount=$((charcount - 1))
|
|
prompt=$'\b \b'
|
|
password="${password%?}"
|
|
else
|
|
prompt=''
|
|
fi
|
|
else
|
|
charcount=$((charcount + 1))
|
|
prompt='*'
|
|
password+="$char"
|
|
fi
|
|
done
|
|
|
|
stty echo
|
|
|
|
}
|
|
|
|
set_shell() {
|
|
|
|
readarray -t optionsAudits <<< "$(grep "zsh\|/bash" /etc/shells | sed 's/\/bin\///g' | sed 's/\/usr//g' | uniq)"
|
|
USER_SHELL="bash"
|
|
|
|
if [[ "${#optionsAudits[@]}" -gt 1 ]]; then
|
|
while :; do
|
|
while [[ ! "${reply}" =~ ^(1|2)$ ]]; do
|
|
i=1
|
|
echo -e "\nChoose default system command shell:\n"
|
|
for o in "${optionsAudits[@]}"; do
|
|
echo "$i) $o"
|
|
((i++)) || true
|
|
done
|
|
if [ -z $PRESET_USER_SHELL ];then
|
|
read -r reply
|
|
else
|
|
reply=1
|
|
for index in "${!optionsAudits[@]}"; do
|
|
if [[ "${optionsAudits[$index]}" == "$PRESET_USER_SHELL" ]]; then
|
|
reply=$(($index + 1))
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
case $reply in
|
|
"1" | "${optionsAudits[0]}")
|
|
USER_SHELL="${optionsAudits[0]}"
|
|
break
|
|
;;
|
|
"2" | "${optionsAudits[1]}")
|
|
USER_SHELL="${optionsAudits[1]}"
|
|
break
|
|
;;
|
|
*)
|
|
USER_SHELL="${optionsAudits[0]}"
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
# Display shell selection only if needs to be selected
|
|
echo -e "\nShell: \x1B[92m${USER_SHELL^^}\x1B[0m"
|
|
fi
|
|
|
|
SHELL_PATH=$(grep "/$USER_SHELL$" /etc/shells | tail -1)
|
|
chsh -s "$(grep -iF "/$USER_SHELL" /etc/shells | tail -1)"
|
|
|
|
# Change shell for future users. useradd reads /etc/default/useradd;
|
|
# /etc/adduser.conf is optional, so guard that edit.
|
|
local _f="/etc/default/useradd"
|
|
if sed "s|^SHELL=.*|SHELL=${SHELL_PATH}|" "${_f}" > "${_f}.tmp"; then
|
|
atomic_write "${_f}.tmp" "${_f}" || return 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
return 1
|
|
fi
|
|
if [[ -f /etc/adduser.conf ]]; then
|
|
_f="/etc/adduser.conf"
|
|
if sed "s|^DSHELL=.*|DSHELL=${SHELL_PATH}|" "${_f}" > "${_f}.tmp"; then
|
|
atomic_write "${_f}.tmp" "${_f}" || return 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
}
|
|
|
|
set_timezone_and_locales() {
|
|
|
|
# Grab this machine's public IP address
|
|
PUBLIC_IP=$(curl --max-time 5 -s https://ipinfo.io/ip)
|
|
|
|
# Check if we have wireless adaptor
|
|
WIFI_DEVICE=$(LC_ALL=C iw dev | awk '$1=="Interface"{print $2}' 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 ""
|
|
unset response
|
|
while [[ ! "${response}" =~ ^(Y|y|N|n)$ ]]; do
|
|
if [ -z $PRESET_CONNECT_WIRELESS ];then
|
|
read -r -p "Connect via wireless? [Y/n] " response
|
|
response=${response:-Y}
|
|
else
|
|
response=n
|
|
fi
|
|
done
|
|
if [[ "${response}" =~ ^(Y|y)$ ]]; then
|
|
|
|
# We could have multiple devices
|
|
if (( $(grep -c . <<<"$WIFI_DEVICE") > 1 )); then
|
|
scanning=0
|
|
while [[ ${scanning} -lt 3 ]]; do
|
|
scanning=$(( scanning + 1 ))
|
|
echo -e "\nMultiple wireless adaptors detected. Choose primary:\n"
|
|
WIFI_DEVICES=($(printf '%s\n' "${WIFI_DEVICE[@]}" | sed 's/^[ \t]*//' | sed 's/"//g' | sed 's/ESSID://' | awk 'BEGIN{FS=OFS=","} {$NF=++count OFS $NF} 1'))
|
|
for str in ${WIFI_DEVICES[@]}; do echo $str | sed "s/,/ \t /g"; done
|
|
echo ""
|
|
read -r -p "Enter a number of wireles adaptor: " input
|
|
if [[ "$input" =~ ^[0-9]{,2}$ && -n "$input" ]] ; then break; fi
|
|
done
|
|
[[ -z $input ]] && input=1
|
|
WIFI_DEVICE=$(echo ${WIFI_DEVICES[$input-1]} | cut -d"," -f2)
|
|
fi
|
|
|
|
# bring up wifi device (not done by networkd, only by NetworkManager)
|
|
ip link set ${WIFI_DEVICE} up
|
|
# get list of wireless networks
|
|
scanning=0
|
|
broken=1
|
|
while [[ ${scanning} -lt 3 ]]; do
|
|
sleep 0.5
|
|
scanning=$(( scanning + 1 ))
|
|
readarray -t ARRAY < <(iw dev ${WIFI_DEVICE} scan 2> /dev/null | egrep 'SSID' | sed 's/^[ \t]*//' | sed 's/"//g' | sed 's/SSID: //' | sed '/^$/d' | sort | uniq | awk 'BEGIN{FS=OFS=","} {$NF=++count OFS $NF} 1')
|
|
if [[ ${#ARRAY[@]} -gt 0 ]]; then broken=0; break; fi
|
|
done
|
|
# wifi can also fail
|
|
if [[ ${broken} == 1 ]]; then
|
|
echo -e "\nWireless connection was \x1B[91mnot detected\x1B[0m.\n"
|
|
else
|
|
echo -e "\nDetected wireless networks:\n"
|
|
scanning=0
|
|
broken=1
|
|
while [[ ${scanning} -lt 3 ]]; do
|
|
scanning=$(( scanning + 1 ))
|
|
while [[ 1 ]] ; do
|
|
for str in "${ARRAY[@]}"; do echo $str | sed "s/,/ \t /"; done
|
|
echo ""
|
|
read -r -p "Enter a number of SSID: " input
|
|
if [[ "$input" =~ ^[0-9]{,2}$ ]] ; then break; fi
|
|
done
|
|
# get password
|
|
while [[ -n "${input}" ]] ; do
|
|
SSID=$(echo ${ARRAY[$input-1]} | cut -d"," -f2-)
|
|
echo ""
|
|
read -r -p "Enter a password for ${SSID}: " password
|
|
break
|
|
done
|
|
|
|
# generate config
|
|
local _f="/etc/netplan/30-wifis-dhcp.yaml"
|
|
if cat <<- EOF > "${_f}.tmp"
|
|
# Created by Armbian firstlogin script
|
|
network:
|
|
wifis:
|
|
${WIFI_DEVICE}:
|
|
dhcp4: yes
|
|
dhcp6: yes
|
|
access-points:
|
|
"$SSID":
|
|
password: "${password}"
|
|
EOF
|
|
then
|
|
atomic_write "${_f}.tmp" "${_f}" 600 || return 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
return 1
|
|
fi
|
|
|
|
# apply to netplan
|
|
systemctl daemon-reload
|
|
netplan apply --timeout 0 2>/dev/null
|
|
|
|
# wireless link probing
|
|
pinging=10
|
|
broken=1
|
|
WIRELESSLINK=""
|
|
echo ""
|
|
while [[ ${pinging} -gt 1 && -n "${input}" && -n "${password}" ]]; do
|
|
pinging=$(( pinging - 1 ))
|
|
printf "\rProbing wireless link ($pinging)"
|
|
WIRELESSLINK=$(iw "${WIFI_DEVICE}" link 2> /dev/null | grep "$SSID")
|
|
sleep 2
|
|
# exit if connection is suffesful
|
|
if [[ "${WIRELESSLINK}" == *$SSID* ]]; then
|
|
broken=0
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ ${broken} == 1 ]]; then
|
|
echo -e "\n\nWireless link was \x1B[91mnot detected\x1B[0m. Wrong password or weak signal."
|
|
fi
|
|
# get public IP probing
|
|
broken=1
|
|
pinging=10
|
|
|
|
while [[ ${pinging} -gt 1 && -n "${input}" && -n "${password}" && "${WIRELESSLINK}" == *$SSID* ]]; do
|
|
pinging=$(( pinging - 1 ))
|
|
printf "\rProbing internet connection ($pinging)"
|
|
PUBLIC_IP=$(curl --max-time 5 -s https://ipinfo.io/ip)
|
|
if [[ -n "$PUBLIC_IP" ]]; then
|
|
broken=0
|
|
break
|
|
else
|
|
sleep 5
|
|
fi
|
|
done
|
|
echo ""
|
|
|
|
if [[ ${broken} == 0 ]]; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ ${broken} == 1 ]]; then
|
|
echo -e "\n\x1B[91mUnable to connect to Access Point\x1B[0m.\n"
|
|
rm -f /etc/netplan/30-wifis-dhcp.yaml
|
|
netplan apply --timeout 0 2>/dev/null
|
|
systemctl daemon-reload
|
|
fi
|
|
|
|
fi # detected or not detected wireless network
|
|
fi
|
|
echo ""
|
|
fi
|
|
fi
|
|
|
|
# Grab IP once again if not found
|
|
sleep 3
|
|
[[ -z "$PUBLIC_IP" && -n "$WIFI_DEVICE" ]] && PUBLIC_IP=$(curl --max-time 5 -s https://ipinfo.io/ip)
|
|
|
|
# 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
|
|
read -r COUNTRYCODE
|
|
echo "${TIMEZONE},${COUNTRY},${COUNTRYCODE}" | tr --delete '"\n'
|
|
done
|
|
)
|
|
|
|
TZDATA=$(echo "${RES}" | cut -d"," -f1)
|
|
CCODE=$(echo "${RES}" | cut -d"," -f3 | xargs)
|
|
|
|
unset response
|
|
while [[ ! "${response}" =~ ^(Y|y|N|n)$ ]]; do
|
|
if [ -z "${SET_LANG_BASED_ON_LOCATION}" ] && [ -n "${TZDATA}" ];then
|
|
echo -e "Detected timezone: \x1B[92m$TZDATA\x1B[0m"
|
|
echo ""
|
|
read -r -p "Set user language based on your location? [Y/n] " response
|
|
response=${response:-Y}
|
|
else
|
|
response=$SET_LANG_BASED_ON_LOCATION
|
|
break
|
|
fi
|
|
done
|
|
# change it only if we have a match and if we agree
|
|
if [[ "${response}" =~ ^(N|n)$ ]]; then
|
|
unset CCODE TZDATA
|
|
fi
|
|
|
|
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)
|
|
readarray -t options <<< "${LOCALES}"
|
|
|
|
if [ -z $PRESET_LOCALE ];then
|
|
# 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
|
|
else
|
|
LOCALES=$PRESET_LOCALE
|
|
fi
|
|
|
|
if [[ "${LOCALES}" != *Skip* ]]; then
|
|
|
|
if [ -z $PRESET_TIMEZONE ];then
|
|
# if TZDATA was not detected, we need to select one
|
|
if [[ -z ${TZDATA} ]]; then
|
|
TZDATA=$(tzselect | tail -1)
|
|
fi
|
|
echo ""
|
|
else
|
|
TZDATA=$PRESET_TIMEZONE
|
|
fi
|
|
|
|
timedatectl set-timezone "${TZDATA}"
|
|
dpkg-reconfigure --frontend=noninteractive tzdata > /dev/null 2>&1
|
|
|
|
# change default locales (atomic write)
|
|
local _f="/etc/default/locale"
|
|
if sed "s/=.*/=$(echo ${LOCALES} | cut -d" " -f1)/g" "${_f}" > "${_f}.tmp"; then
|
|
atomic_write "${_f}.tmp" "${_f}" || return 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
return 1
|
|
fi
|
|
|
|
# generate locales (atomic write for locale.gen)
|
|
local _f="/etc/locale.gen"
|
|
if sed 's/# '"${LOCALES}"'/'"${LOCALES}"'/' "${_f}" > "${_f}.tmp"; then
|
|
atomic_write "${_f}.tmp" "${_f}" || return 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
return 1
|
|
fi
|
|
echo -e "Generating locales: \x1B[92m${LOCALES}\x1B[0m"
|
|
locale-gen "${LOCALES}" > /dev/null 2>&1
|
|
|
|
# setting detected locales only for user (idempotent)
|
|
for rcfile in /home/"$RealUserName"/.bashrc /home/"$RealUserName"/.xsessionrc; do
|
|
if [[ ! -e "$rcfile" ]]; then
|
|
# Create empty file with proper ownership before appending; leave
|
|
# ownership alone if the file already exists (user may have customized).
|
|
touch -- "$rcfile"
|
|
chown -- "$RealUserName":"$RealUserName" "$rcfile"
|
|
fi
|
|
if ! grep -q "^export LC_ALL=$LOCALES" "$rcfile" 2>/dev/null; then
|
|
{
|
|
cat -- "$rcfile"
|
|
echo "export LC_ALL=$LOCALES"
|
|
echo "export LANG=$LOCALES"
|
|
echo "export LANGUAGE=$LOCALES"
|
|
} > "${rcfile}.tmp" && atomic_write "${rcfile}.tmp" "$rcfile" "" "$RealUserName:$RealUserName" || return 1
|
|
fi
|
|
done
|
|
|
|
fi
|
|
}
|
|
|
|
add_profile_sync_settings() {
|
|
if [[ ! -f /usr/bin/psd ]]; then
|
|
return 0
|
|
fi
|
|
|
|
/usr/bin/psd > /dev/null 2>&1
|
|
config_file="${HOME}/.config/psd/psd.conf"
|
|
if [ -f "${config_file}" ]; then
|
|
# test for overlayfs
|
|
sed -i 's/#USE_OVERLAYFS=.*/USE_OVERLAYFS="yes"/' "${config_file}"
|
|
case $(/usr/bin/psd p 2> /dev/null | grep Overlayfs) in
|
|
*active*)
|
|
echo -e "\nConfigured profile sync daemon with overlayfs."
|
|
;;
|
|
*)
|
|
echo -e "\nConfigured profile sync daemon."
|
|
sed -i 's/USE_OVERLAYFS="yes"/#USE_OVERLAYFS="no"/' "${config_file}"
|
|
;;
|
|
esac
|
|
fi
|
|
systemctl --user enable psd.service > /dev/null 2>&1
|
|
systemctl --user start psd.service > /dev/null 2>&1
|
|
}
|
|
|
|
add_user() {
|
|
read -r -t 0 _
|
|
REPEATS=3
|
|
|
|
# Power-loss re-run: detect existing regular user, skip username prompt
|
|
local existing_user
|
|
existing_user=$(awk -F: '$3 >= 1000 && $1 != "nobody" {print $1; exit}' /etc/passwd)
|
|
if [ -n "${existing_user}" ]; then
|
|
RealUserName="${existing_user}"
|
|
username="${existing_user}"
|
|
echo -e "Detected existing user \e[0;92m$RealUserName\x1B[0m from previous setup, reusing."
|
|
else
|
|
while [ -f "/root/.not_logged_in_yet" ]; do
|
|
[[ -z "${PRESET_USER_NAME}" ]] && echo -e "\nPlease provide a username (eg. your first name): \c"
|
|
if [ -z "$PRESET_USER_NAME" ];then
|
|
read -r -e username
|
|
else
|
|
username="$PRESET_USER_NAME"
|
|
fi
|
|
if ! grep '^[a-zA-Z][a-zA-Z0-9]*$' <<< "$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
|
|
elif [ ! -d "/home/$RealUserName" ]; then
|
|
# Partially created user from a prior interrupted run: break and let
|
|
# the home-repair branch below recover it. Without this, PRESET_USER_NAME
|
|
# would loop forever on "already exists".
|
|
break
|
|
else
|
|
echo -e "Username \e[0;31m$RealUserName\x1B[0m already exists on the system."
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Set default user login icon
|
|
set_user_icon "$RealUserName"
|
|
|
|
while [ -f "/root/.not_logged_in_yet" ]; do
|
|
if [ -z "$PRESET_USER_PASSWORD" ];then
|
|
read_password "Create user ($username)"
|
|
echo ""
|
|
else
|
|
password="$PRESET_USER_PASSWORD"
|
|
fi
|
|
first_input="$password"
|
|
if [ -z "$PRESET_USER_PASSWORD" ];then
|
|
read_password "Repeat user ($username)"
|
|
echo ""
|
|
else
|
|
password="$PRESET_USER_PASSWORD"
|
|
fi
|
|
second_input="$password"
|
|
if [[ "$first_input" == "$second_input" ]]; then
|
|
# minimal images might not have this
|
|
if command -v cracklib-check > /dev/null 2>&1; then
|
|
result="$(cracklib-check <<< "$password")"
|
|
okay="$(awk -F': ' '{ print $2}' <<< "$result")"
|
|
if [[ "$okay" != "OK" ]]; then
|
|
echo -e "\n\e[0;31mWarning:\x1B[0m Weak password, $okay \b!"
|
|
fi
|
|
fi
|
|
if [ -z "$PRESET_DEFAULT_REALNAME" ];then
|
|
echo -e ""
|
|
read -r -e -p "Please provide your real name: " -i "${RealUserName^}" RealName
|
|
else
|
|
RealName="$PRESET_DEFAULT_REALNAME"
|
|
fi
|
|
|
|
# Skip user creation if already exists (power-loss re-run). If the user
|
|
# exists but home is missing (passwd written, home creation interrupted),
|
|
# rebuild it from /etc/skel with correct ownership so the user can log in.
|
|
if ! id "$RealUserName" > /dev/null 2>&1; then
|
|
# useradd (Essential, from passwd) instead of adduser (optional on
|
|
# Debian forky+, absent from minimal images). --user-group mirrors
|
|
# adduser's per-user group; --create-home copies /etc/skel; login shell
|
|
# comes from SHELL_PATH (set_shell ran first). Password is disabled here
|
|
# and set right below, same as adduser --disabled-password.
|
|
useradd --create-home --home-dir /home/"$RealUserName" --shell "$SHELL_PATH" \
|
|
--comment "$RealName" --user-group "$RealUserName" || return 1
|
|
elif [ ! -d "/home/$RealUserName" ]; then
|
|
mkdir -p /home/"$RealUserName" || return 1
|
|
cp -r /etc/skel/. /home/"$RealUserName"/ 2>/dev/null || return 1
|
|
chown -R "$RealUserName:$RealUserName" /home/"$RealUserName" || return 1
|
|
echo -e "Repaired missing home directory for \e[0;92m$RealUserName\x1B[0m."
|
|
fi
|
|
|
|
# download and add SSH key if defined
|
|
if [[ -n "${PRESET_USER_KEY}" ]]; then
|
|
local _f="/home/$RealUserName/.ssh/authorized_keys"
|
|
mkdir -p /home/"$RealUserName"/.ssh/ || return 1
|
|
chown "$RealUserName:$RealUserName" /home/"$RealUserName"/.ssh/ || return 1
|
|
chmod 700 /home/"$RealUserName"/.ssh/ || return 1
|
|
if curl --retry 5 --connect-timeout 3 "${PRESET_USER_KEY}" > "${_f}.tmp" 2> /dev/null; then
|
|
atomic_write "${_f}.tmp" "${_f}" 600 "$RealUserName:$RealUserName" || return 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
if [[ -n "$first_input" ]]; then
|
|
(
|
|
echo "$first_input"
|
|
echo "$second_input"
|
|
) | passwd "$RealUserName" > /dev/null 2>&1
|
|
else
|
|
passwd -d "$RealUserName" > /dev/null 2>&1
|
|
fi
|
|
|
|
# Pre-create docker group to ensure user membership is set up before Docker installation.
|
|
# (docker-ce package creates this group automatically during postinst, but we create it early
|
|
# to guarantee group membership is ready immediately after user creation.)
|
|
if ! getent group docker >/dev/null; then
|
|
if ! groupadd --system docker 2>/dev/null; then
|
|
echo "Warning: Failed to create docker group" >&2
|
|
fi
|
|
fi
|
|
for additionalgroup in sudo netdev audio video disk tty users games dialout plugdev input bluetooth systemd-journal ssh render docker; do
|
|
usermod -aG "${additionalgroup}" "${RealUserName}" 2> /dev/null
|
|
done
|
|
|
|
# fix for gksu in Xenial
|
|
touch /home/"$RealUserName"/.Xauthority
|
|
chown "$RealUserName":"$RealUserName" /home/"$RealUserName"/.Xauthority
|
|
RealName="$(awk -F":" "/^${RealUserName}:/ {print \$5}" < /etc/passwd | cut -d',' -f1)"
|
|
[ -z "$RealName" ] && RealName="$RealUserName"
|
|
echo -e "\nDear \e[0;92m${RealName}\x1B[0m, your account \e[0;92m${RealUserName}\x1B[0m has been created and is sudo enabled."
|
|
echo -e "Please use this account for your daily work from now on.\n"
|
|
# .not_logged_in_yet is NOT deleted here - the EXIT trap removes
|
|
# it once FIRSTLOGIN_SUCCESS=1 is set after all config completes.
|
|
# set up profile sync daemon on desktop systems
|
|
if command -v psd > /dev/null 2>&1; then
|
|
# Grant psd-overlay-helper NOPASSWD via a sudoers drop-in (atomic write).
|
|
# Using /etc/sudoers.d/ avoids touching /etc/sudoers directly: power-loss
|
|
# recovery is simply "no drop-in" → re-run recreates it, and there is no
|
|
# need to restore or validate any dist version of /etc/sudoers.
|
|
local _f="/etc/sudoers.d/armbian-firstlogin-psd"
|
|
if echo "${RealUserName} ALL=(ALL) NOPASSWD: /usr/bin/psd-overlay-helper" > "${_f}.tmp"; then
|
|
atomic_write "${_f}.tmp" "${_f}" 440 || return 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
return 1
|
|
fi
|
|
touch /home/"${RealUserName}"/.activate_psd
|
|
chown "$RealUserName":"$RealUserName" /home/"${RealUserName}"/.activate_psd
|
|
fi
|
|
break
|
|
elif [[ -n $password ]]; then
|
|
echo -e "Rejected - \e[0;31mpasswords do not match.\x1B[0m Try again [${REPEATS}]."
|
|
REPEATS=$((REPEATS - 1))
|
|
fi
|
|
[[ "$REPEATS" -eq 0 ]] && exit 0
|
|
done
|
|
|
|
}
|
|
|
|
set_user_icon() {
|
|
local U="$1"
|
|
local ICON_SRC="/usr/share/armbian/armbian-user-icon.png"
|
|
local ICON_DST="/var/lib/AccountsService/icons/$U"
|
|
local META="/var/lib/AccountsService/users/$U"
|
|
|
|
# If the icon doesn't exist, do nothing.
|
|
[ -f "$ICON_SRC" ] || return 0
|
|
|
|
mkdir -p /var/lib/AccountsService/icons /var/lib/AccountsService/users
|
|
|
|
# Copy icon
|
|
install -m 0644 "$ICON_SRC" "$ICON_DST"
|
|
|
|
# Write AccountsService metadata
|
|
cat <<- EOF > "$META"
|
|
[User]
|
|
Icon=$ICON_DST
|
|
SystemAccount=false
|
|
EOF
|
|
chown root:root "$ICON_DST" "$META"
|
|
chmod 0644 "$ICON_DST" "$META"
|
|
}
|
|
|
|
|
|
if [[ -f /root/.not_logged_in_yet ]] && tty -s; then
|
|
|
|
# All autologin consoles start the setup at once and the one that wins is
|
|
# often a console nobody is at, so let the newest session take it over.
|
|
# SIGTERM is the holder's clean-exit path: the marker survives a power
|
|
# loss the same way, and the setup simply runs again here.
|
|
exec 9<> /run/armbian-firstlogin.lock || exit 1
|
|
until flock -n 9; do
|
|
kill -TERM "$(< /run/armbian-firstlogin.lock)" 2> /dev/null
|
|
sleep 1
|
|
done
|
|
echo $$ > /run/armbian-firstlogin.lock
|
|
|
|
# it may have been finished by the session we just displaced
|
|
[[ -f /root/.not_logged_in_yet ]] || exit 0
|
|
|
|
. /root/.not_logged_in_yet
|
|
|
|
# override configuration from URL
|
|
if [[ -n "${PRESET_CONFIGURATION}" ]]; then
|
|
_f="/root/.not_logged_in_yet"
|
|
if curl --retry 5 --connect-timeout 3 "${PRESET_CONFIGURATION}" > "${_f}.tmp" 2> /dev/null && bash -n "${_f}.tmp"; then
|
|
atomic_write "${_f}.tmp" "${_f}" 600 || exit 1
|
|
. "${_f}"
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
do_firstrun_automated_network_configuration || exit 1
|
|
|
|
# disable autologin (remove both generic and tty-specific paths)
|
|
rm -f /etc/systemd/system/getty@.service.d/override.conf
|
|
rm -f /etc/systemd/system/getty@tty1.service.d/override.conf
|
|
rm -f /etc/systemd/system/serial-getty@.service.d/override.conf
|
|
rm -f /etc/systemd/system/serial-getty@ttyGS0.service.d/override.conf
|
|
systemctl daemon-reload
|
|
|
|
declare desktop_dm="none"
|
|
declare -i desktop_is_sddm=0 desktop_is_lightdm=0 desktop_is_gdm3=0
|
|
if [[ -f /usr/bin/sddm ]]; then
|
|
desktop_dm="sddm"
|
|
desktop_is_sddm=1
|
|
fi
|
|
if [[ -f /usr/sbin/lightdm ]]; then
|
|
desktop_dm="lightdm"
|
|
desktop_is_lightdm=1
|
|
fi
|
|
if [[ -f /usr/sbin/gdm3 ]]; then
|
|
desktop_dm="gdm3"
|
|
desktop_is_gdm3=1
|
|
fi
|
|
|
|
echo -e "\nWaiting for system to finish booting ..."
|
|
systemctl is-system-running --wait > /dev/null
|
|
|
|
# enable networkManager-wait-online.service
|
|
# When NM is used the NetworkManager-wait-online.service must be enabled so that network-online.target is not reached until
|
|
# NetworkManager has brought up all the interfaces. Service units that require the network to be up before starting rely
|
|
# on network-online.target working correctly otherwise they will likely fail on boot
|
|
# Same goes for systemd-networkd stack
|
|
# https://github.com/armbian/build/issues/7896
|
|
if
|
|
systemctl is-enabled --quiet NetworkManager &&
|
|
! systemctl is-enabled --quiet NetworkManager-wait-online
|
|
then
|
|
systemctl enable NetworkManager-wait-online
|
|
# @TODO: determine if there is any value in starting it now
|
|
echo "Waiting for network startup to complete..."
|
|
systemctl start NetworkManager-wait-online
|
|
fi
|
|
|
|
if
|
|
systemctl is-enabled --quiet systemd-networkd &&
|
|
! systemctl is-enabled --quiet systemd-networkd-wait-online
|
|
then
|
|
systemctl enable systemd-networkd-wait-online
|
|
# @TODO: determine if there is any value in starting it now
|
|
echo "Waiting for network startup to complete..."
|
|
systemctl start systemd-networkd-wait-online
|
|
fi
|
|
|
|
# Enable HiDPI support only if the framebuffer size is detectable
|
|
FB_VIRTUAL_SIZE="/sys/class/graphics/fb0/virtual_size"
|
|
HIDPI_THRESHOLD="1920"
|
|
if [[ -r "$FB_VIRTUAL_SIZE" ]]; then
|
|
fb_virtual_width=$(cut -d, -f1 < "$FB_VIRTUAL_SIZE" 2>/dev/null)
|
|
if [[ "$fb_virtual_width" =~ ^[0-9]+$ && "$fb_virtual_width" -gt "${HIDPI_THRESHOLD}" ]]; then
|
|
# Enable HiDPI in LightDM slick-greeter
|
|
if [[ -f /etc/lightdm/slick-greeter.conf ]]; then
|
|
if ! grep -q "^enable-hidpi *= *on" /etc/lightdm/slick-greeter.conf; then
|
|
echo "enable-hidpi = on" >> /etc/lightdm/slick-greeter.conf
|
|
fi
|
|
fi
|
|
|
|
# Set XFCE scaling factor in skeleton config
|
|
XFCE_XSETTINGS="/etc/skel/.config/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml"
|
|
if [[ -f "$XFCE_XSETTINGS" ]]; then
|
|
sed -i 's|\(<property name="WindowScalingFactor" type="int" value="\)[^"]*\(">\)|\12\2|' "$XFCE_XSETTINGS"
|
|
fi
|
|
|
|
# Set a larger console font for framebuffer
|
|
if [[ -f /usr/share/consolefonts/Uni3-TerminusBold32x16.psf.gz ]]; then
|
|
setfont /usr/share/consolefonts/Uni3-TerminusBold32x16.psf.gz
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
clear
|
|
|
|
# In case VENDORPRETTYNAME is defined, display that
|
|
[[ -n "${VENDORPRETTYNAME}" ]] && VENDOR="$VENDORPRETTYNAME"
|
|
echo -e "Welcome to \e[1m\e[97m${VENDOR}\x1B[0m! \n"
|
|
echo -e "Documentation: \e[1m\e[92m${VENDORDOCS}\x1B[0m | Community support: \e[1m\e[92m${VENDORSUPPORT}\x1B[0m\n"
|
|
|
|
echo "" # empty line
|
|
|
|
# Try to get the local IP address (script halts until IP was found or x retries were done)
|
|
get_local_ip_addr
|
|
|
|
if systemctl is-active --quiet armbian-web-config.service; then
|
|
WEB_SSID="$(cat /etc/hostname)-armbiansetup"
|
|
echo -e "\n\e[1m\e[96mWeb Configuration Available!\x1B[0m"
|
|
echo -e "Connect to Wi-Fi SSID: \e[1m\e[92m${WEB_SSID}\x1B[0m"
|
|
echo -e "Then open \e[1mhttp://10.42.0.1\x1B[0m in your browser"
|
|
echo -e "Or continue with CLI setup below...\n"
|
|
fi
|
|
|
|
[[ -z "$PRESET_ROOT_PASSWORD" ]] && echo "" # empty line
|
|
|
|
trap '' 2
|
|
REPEATS=3
|
|
while [ -f "/root/.not_logged_in_yet" ]; do
|
|
. /root/.not_logged_in_yet
|
|
if [ -z "$PRESET_ROOT_PASSWORD" ];then
|
|
read_password "Create root"
|
|
else
|
|
# allow automated install also in interactive session
|
|
#if [ "$(who am i | awk '{print $2}')" != "tty1" ];then
|
|
# exit
|
|
#fi
|
|
password="$PRESET_ROOT_PASSWORD"
|
|
# download SSH key if defined
|
|
if [[ -n "${PRESET_ROOT_KEY}" ]]; then
|
|
mkdir -p /root/.ssh/
|
|
_f="/root/.ssh/authorized_keys"
|
|
if curl --retry 5 --connect-timeout 3 "${PRESET_ROOT_KEY}" > "${_f}.tmp" 2> /dev/null; then
|
|
atomic_write "${_f}.tmp" "${_f}" 600 || exit 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# enable motd
|
|
chmod +x /etc/update-motd.d/*
|
|
|
|
first_input="$password"
|
|
if [ -z "$PRESET_ROOT_PASSWORD" ];then
|
|
echo ""
|
|
read_password "Repeat root"
|
|
echo ""
|
|
else
|
|
password="$PRESET_ROOT_PASSWORD"
|
|
fi
|
|
second_input="$password"
|
|
if [[ "$first_input" == "$second_input" ]]; then
|
|
# minimal might not have this
|
|
if command -v cracklib-check > /dev/null 2>&1; then
|
|
result="$(cracklib-check <<< "$password")"
|
|
okay="$(awk -F': ' '{ print $2}' <<< "$result")"
|
|
if [[ "$okay" != "OK" ]]; then
|
|
echo -e "\n\e[0;31mWarning:\x1B[0m Weak password, $okay \b!"
|
|
fi
|
|
fi
|
|
(
|
|
echo "$first_input"
|
|
echo "$second_input"
|
|
) | passwd root > /dev/null 2>&1
|
|
break
|
|
elif [[ -n $password ]]; then
|
|
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
|
|
|
|
# display support status
|
|
if [ "$IMAGE_TYPE" != "nightly" ]; then
|
|
if [[ "$BRANCH" == "edge" ]]; then
|
|
echo -e "\nSupport status: \e[0;31mcommunity support\x1B[0m (edge kernel branch)"
|
|
elif [[ "$DISTRIBUTION_STATUS" != "supported" ]]; then
|
|
echo -e "\nSupport status: \e[0;31mcommunity support\x1B[0m (unsupported userspace)"
|
|
elif [[ "$BOARD_TYPE" != "conf" ]]; then
|
|
echo -e "\nSupport status: \e[0;31mcommunity support\x1B[0m (looking for a dedicated maintainer)"
|
|
fi
|
|
else
|
|
|
|
echo -e "\e[0;31m\nDeveloper Preview Build\x1B[0m\n"
|
|
echo -e "This Armbian image was generated automatically for development and testing purpose."
|
|
echo -e "It may include unfinished features or unstable components.\n"
|
|
echo -e "If you are not here to report issues or just test it, \e[0;31mplease do not use this image in production.\x1B[0m"
|
|
echo -e "Expect things to change — or even break — as improvements are made."
|
|
|
|
fi
|
|
|
|
# ask user to select shell
|
|
trap '' 2
|
|
set_shell || exit 1
|
|
trap - INT
|
|
|
|
trap check_abort INT
|
|
|
|
if [ -f "/root/.not_logged_in_yet" ]; then
|
|
[[ -z "${PRESET_USER_NAME}" ]] && echo -e "\nCreating a new user account. Press <Ctrl-C> to abort"
|
|
[[ "${desktop_dm}" != "none" ]] && echo -e "\n\e[0;31mDesktop environment will not be enabled if you abort the new user creation\x1B[0m"
|
|
add_user
|
|
if [[ $? -ne 0 || -z "${RealUserName:-}" ]]; then
|
|
echo -e "\nUser creation failed. Retrying on next login."
|
|
exit 1
|
|
fi
|
|
fi
|
|
trap - INT
|
|
|
|
# ask user to select automated locales or not
|
|
trap '' 2
|
|
set_timezone_and_locales || exit 1
|
|
trap - INT
|
|
|
|
if [[ ${USER_SHELL} == zsh ]]; then
|
|
printf "\nYou selected \e[0;91mZSH\x1B[0m as your default shell. If you want to use it right away, please logout and login! \n\n"
|
|
fi
|
|
|
|
# re-enable passing locale environment via ssh (atomic write)
|
|
_f="/etc/ssh/sshd_config"
|
|
if sed -E '/^[[:blank:]]*#[[:blank:]]*AcceptEnv[[:blank:]]+LANG/ s/^[[:blank:]]*#[[:blank:]]*//' "${_f}" > "${_f}.tmp"; then
|
|
atomic_write "${_f}.tmp" "${_f}" || exit 1
|
|
else
|
|
rm -f "${_f}.tmp"
|
|
exit 1
|
|
fi
|
|
# Restart ssh.service; on failure preserve /root/.not_logged_in_yet so
|
|
# firstlogin reruns for recovery. Unlike reload, restart succeeds on an
|
|
# inactive unit, so this does not loop on a fresh image.
|
|
systemctl restart ssh.service || exit 1
|
|
# All critical configuration is complete. The EXIT trap may now safely
|
|
# remove the marker; subsequent failures (DM startup, etc.) should not
|
|
# force a full firstlogin rerun.
|
|
FIRSTLOGIN_SUCCESS=1
|
|
|
|
# rpardini: hacks per-dm, very much legacy stuff that works by a miracle
|
|
if [[ "${desktop_dm}" == "lightdm" ]] && [ -n "$RealName" ]; then
|
|
|
|
mkdir -p /etc/lightdm/lightdm.conf.d
|
|
cat <<- EOF > /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
[Seat:*]
|
|
autologin-user=$RealUserName
|
|
autologin-user-timeout=0
|
|
user-session=xfce
|
|
EOF
|
|
|
|
# select gnome session (has to be first or it breaks budgie/cinnamon desktop autologin and user-session)
|
|
# @TODO: remove this, gnome should use gdm3, not lightdm
|
|
[[ -x $(command -v gnome-session) ]] && sed -i "s/user-session.*/user-session=ubuntu/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v gnome-session) ]] && sed -i "s/user-session.*/user-session=ubuntu/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select awesome session
|
|
[[ -x $(command -v awesome) ]] && sed -i "s/user-session.*/user-session=awesome/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v awesome) ]] && sed -i "s/user-session.*/user-session=awesome/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select budgie session
|
|
[[ -x $(command -v budgie-desktop) ]] && sed -i "s/user-session.*/user-session=budgie-desktop/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v budgie-desktop) ]] && sed -i "s/user-session.*/user-session=budgie-desktop/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select cinnamon session
|
|
[[ -x $(command -v cinnamon) ]] && sed -i "s/user-session.*/user-session=cinnamon/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v cinnamon) ]] && sed -i "s/user-session.*/user-session=cinnamon/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select deepin session
|
|
[[ -x $(command -v deepin-wm) ]] && sed -i "s/user-session.*/user-session=deepin/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v deepin-wm) ]] && sed -i "s/user-session.*/user-session=deepin/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select ice-wm session
|
|
[[ -x $(command -v icewm-session) ]] && sed -i "s/user-session.*/user-session=icewm-session/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v icewm-session) ]] && sed -i "s/user-session.*/user-session=icewm-session/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select i3 session
|
|
[[ -x $(command -v i3) ]] && sed -i "s/user-session.*/user-session=i3/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v i3) ]] && sed -i "s/user-session.*/user-session=i3/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select lxde session
|
|
[[ -x $(command -v startlxde) ]] && sed -i "s/user-session.*/user-session=LXDE/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v startlxde) ]] && sed -i "s/user-session.*/user-session=LXDE/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select lxqt session
|
|
[[ -x $(command -v startlxqt) ]] && sed -i "s/user-session.*/user-session=lxqt/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v startlxqt) ]] && sed -i "s/user-session.*/user-session=lxqt/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select mate session
|
|
[[ -x $(command -v mate-wm) ]] && sed -i "s/user-session.*/user-session=mate/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v mate-wm) ]] && sed -i "s/user-session.*/user-session=mate/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select sway wayland session
|
|
[[ -x $(command -v sway) ]] && sed -i "s/user-session.*/user-session=sway/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
[[ -x $(command -v sway) ]] && sed -i "s/user-session.*/user-session=sway/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
|
|
# select xmonad session. The xmonad package ships two of them:
|
|
# xmonad.desktop runs the window manager bare, while
|
|
# gnome-flashback-xmonad.desktop runs it inside a GNOME Flashback
|
|
# session and so gets a session dbus, a polkit agent and session
|
|
# management. Prefer the latter when it can actually start -- its
|
|
# TryExec is gnome-flashback, which comes from gnome-session-flashback
|
|
# and is not always installed -- and fall back to the bare session.
|
|
if [[ -x $(command -v xmonad) ]]; then
|
|
xmonad_session="xmonad"
|
|
if [[ -f /usr/share/xsessions/gnome-flashback-xmonad.desktop && -x $(command -v gnome-flashback) ]]; then
|
|
xmonad_session="gnome-flashback-xmonad"
|
|
fi
|
|
sed -i "s/user-session.*/user-session=${xmonad_session}/" /etc/lightdm/lightdm.conf.d/11-armbian.conf
|
|
sed -i "s/user-session.*/user-session=${xmonad_session}/" /etc/lightdm/lightdm.conf.d/22-armbian-autologin.conf
|
|
fi
|
|
|
|
ln -sf /lib/systemd/system/lightdm.service /etc/systemd/system/display-manager.service
|
|
|
|
if [[ -f /var/run/resize2fs-reboot ]]; then
|
|
# Let the user reboot now otherwise start desktop environment
|
|
printf "\n\n\e[0;91mWarning: a reboot is needed to finish resizing the filesystem \x1B[0m \n"
|
|
printf "\e[0;91mPlease reboot the system now \x1B[0m \n\n"
|
|
else
|
|
echo -e "\n\e[1m\e[39mNow starting desktop environment...\x1B[0m\n"
|
|
sleep 1
|
|
systemctl --no-block start lightdm 2>/dev/null
|
|
if [ -f /root/.desktop_autologin ]; then
|
|
rm /root/.desktop_autologin
|
|
else
|
|
systemctl -q enable armbian-disable-autologin.timer
|
|
systemctl start armbian-disable-autologin.timer
|
|
fi
|
|
# logout if logged at console
|
|
who -la | grep root | grep -q tty1 && exit 1
|
|
fi
|
|
|
|
elif [[ "${desktop_dm}" == "gdm3" ]] && [ -n "$RealName" ]; then
|
|
# 1st run goes without login
|
|
mkdir -p /etc/gdm3
|
|
cat <<- EOF > /etc/gdm3/custom.conf
|
|
[daemon]
|
|
AutomaticLoginEnable = true
|
|
AutomaticLogin = $RealUserName
|
|
EOF
|
|
|
|
ln -sf /lib/systemd/system/gdm3.service /etc/systemd/system/display-manager.service
|
|
|
|
if [[ -f /var/run/resize2fs-reboot ]]; then
|
|
# Let the user reboot now otherwise start desktop environment
|
|
printf "\n\n\e[0;91mWarning: a reboot is needed to finish resizing the filesystem \x1B[0m \n"
|
|
printf "\e[0;91mPlease reboot the system now \x1B[0m \n\n"
|
|
else
|
|
echo -e "\n\e[1m\e[39mNow starting desktop environment...\x1B[0m\n"
|
|
sleep 1
|
|
systemctl --no-block start gdm3 2>/dev/null
|
|
if [ -f /root/.desktop_autologin ]; then
|
|
rm /root/.desktop_autologin
|
|
else
|
|
(
|
|
sleep 20
|
|
sed -i "s/AutomaticLoginEnable.*/AutomaticLoginEnable = false/" /etc/gdm3/custom.conf
|
|
) &
|
|
fi
|
|
# logout if logged at console
|
|
who -la | grep root | grep -q tty1 && exit 1
|
|
fi
|
|
elif [[ "${desktop_dm}" == "sddm" ]] && [ -n "$RealName" ]; then
|
|
|
|
# create default sddm config
|
|
mkdir -p /etc/sddm.conf.d
|
|
|
|
if [ ! -e "/etc/sddm.conf.d/armbian.conf" ]; then
|
|
cat <<- EOF > /etc/sddm.conf.d/armbian.conf
|
|
[Theme]
|
|
Current=breeze
|
|
[General]
|
|
InputMethod=none
|
|
EOF
|
|
fi
|
|
|
|
# 1st run goes without login
|
|
cat <<- EOF > /etc/sddm.conf.d/autologin.conf
|
|
[Autologin]
|
|
User=$RealUserName
|
|
EOF
|
|
echo -e "\n\e[1m\e[39mNow starting desktop environment via ${desktop_dm}...\x1B[0m\n"
|
|
systemctl enable sddm 2>/dev/null
|
|
systemctl --no-block start sddm 2>/dev/null
|
|
|
|
if [ -f /root/.desktop_autologin ]; then
|
|
rm /root/.desktop_autologin
|
|
else
|
|
systemctl -q enable armbian-disable-autologin.timer
|
|
fi
|
|
# logout if logged at console
|
|
who -la | grep root | grep -q tty1 && exit 1
|
|
|
|
else
|
|
# no display manager detected -> clear screen and show motd
|
|
clear
|
|
run-parts --lsbsysinit /etc/cron.daily
|
|
run-parts --lsbsysinit /etc/update-motd.d
|
|
|
|
# Display reboot recommendation if necessary
|
|
if [[ -f /var/run/resize2fs-reboot ]]; then
|
|
printf "\n\n\e[0;91mWarning: a reboot is needed to finish resizing the filesystem \x1B[0m \n"
|
|
printf "\e[0;91mPlease reboot the system now \x1B[0m \n\n"
|
|
fi
|
|
|
|
fi
|
|
fi
|
|
|
|
# Run provisioning script if exists
|
|
if [[ -f /root/provisioning.sh ]]; then
|
|
. /root/provisioning.sh
|
|
fi
|
|
|
|
# Marker cleanup is handled by _firstlogin_cleanup EXIT trap.
|