diff --git a/README.md b/README.md index c906df8..c15bca8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Please follow [Alpine Linux Wiki](https://wiki.alpinelinux.org/wiki/Installation Tools provided here can be used on any plaform for any install modes (diskless, data disk, system disk). Just add [**headless.apkovl.tar.gz**](https://is.gd/apkovl_master)[^2] overlay file *as-is* at the root of Alpine Linux boot media (or onto any custom side-media) and boot-up the system.\ -With default DCHP-based network interface definitions (and [SSID/pass file](#extra-configuration) if using wifi), system can then be remotely accessed with: `ssh root@`\ +With default DCHP-based network interface definitions (and [SSID/pass](#extra-configuration) file if using wifi), system can then be remotely accessed with: `ssh root@`\ (system IP address may be determined with any IP scanning tools such as `nmap`). As with Alpine Linux initial bring-up, `root` account has no password initially (change that during target setup!).\ @@ -29,7 +29,7 @@ Extra files may be added next to `headless.apkovl.tar.gz` to customise boostrapp **Goody:** seamless USB-serial & USB-ethernet gadget mode (PiZero for instance):\ On supporting Pi devices, just add `dtoverlay=dwc2,dr_mode=peripheral` in `usercfg.txt` (or `config.txt`), and plug USB cable into host Computer port.\ -Serial terminal can then be connected-to from host Computer (xon/xoff flow control: e.g. on Linux with `cu -l ttyACM0`).\ +Serial terminal can then be connected-to from host Computer (e.g. `cu -l ttyACM0` on Linux. xon/xoff flow control).\ Alternatively, with host Computer set-up to share networking with USB interface as 10.42.0.1 gateway, one can log into device from host with: `ssh root@10.42.0.2`. Main execution steps are logged: `cat /var/log/messages | grep headless`. diff --git a/headless.apkovl.tar.gz b/headless.apkovl.tar.gz index c468d2f..b63ae17 100644 Binary files a/headless.apkovl.tar.gz and b/headless.apkovl.tar.gz differ diff --git a/make.sh b/make.sh index f1a4ee9..5790b46 100755 --- a/make.sh +++ b/make.sh @@ -3,28 +3,33 @@ # SPDX-FileCopyrightText: Copyright 2022-2023, macmpi # SPDX-License-Identifier: MIT - +# scrip meant to be run on Alpine (busybox) or on Ubuntu command -v doas > /dev/null || alias doas="/usr/bin/sudo" build_path="$(mktemp -d)" if [ -n "$build_path" ]; then - cp -r overlay "$build_path"/. - find "$build_path"/overlay/ -exec touch -md "$(date '+%F 00:00:00')" {} \; - - # setting owner/groups for runtime (won't affect mtime) + cp -a overlay "$build_path"/. + # busybox config on Alpine & Ubuntu has FEATURE_TAR_GNU_EXTENSIONS + # so will preserve user/group/modes & mtime + find "$build_path"/overlay/ -exec sh -c 'TZ=UTC touch -chm -t $( TZ=UTC date +%Y%m%d0000.00 ) $0' {} \; + # setting modes and owner/groups for runtime (won't affect mtime) find "$build_path"/overlay/etc -type d -exec chmod 755 {} \; - chmod +x "$build_path"/overlay/etc/init.d/* - find "$build_path"/overlay/usr -type d -exec chmod 755 {} \; - chmod +x "$build_path"/overlay/usr/local/bin/* + chmod 755 "$build_path"/overlay/etc/init.d/* + chmod 755 "$build_path"/overlay/etc/runlevels/default/* chmod 777 "$build_path"/overlay/tmp chmod 700 "$build_path"/overlay/tmp/.trash - chmod 600 "$build_path"/overlay/tmp/.trash/ssh_host_*_key - doas chown -R 0:0 "$build_path"/overlay/* - - doas tar -cvf "$build_path"/headless.apkovl.tar -C "$build_path"/overlay etc usr tmp - gzip -nk9 "$build_path"/headless.apkovl.tar && mv "$build_path"/headless.apkovl.tar.gz . - touch -md "$(date '+%F 00:00:00')" headless.apkovl.tar.gz + chmod -R 600 "$build_path"/overlay/tmp/.trash/ssh_host_*_key + find "$build_path"/overlay/usr -type d -exec chmod 755 {} \; + chmod 755 "$build_path"/overlay/usr/local/bin/* + # busybox config on Ubuntu does not enable use of user and group names in tar + # (FEATURE_TAR_UNAME_GNAME not set) + doas chown -Rh 0:0 "$build_path"/overlay/* + # shellcheck disable=SC2046 # we want word splitting as result of find + doas tar cv -C "$build_path"/overlay --no-recursion \ + $(doas find "$build_path"/overlay/ | sed "s|$build_path/overlay/||" | sort | xargs ) | \ + gzip -c9n > headless.apkovl.tar.gz + TZ=UTC touch -cm -t "$( TZ=UTC date +%Y%m%d0000.00 )" headless.apkovl.tar.gz doas rm -rf "$build_path" fi diff --git a/overlay/usr/local/bin/headless_bootstrap b/overlay/usr/local/bin/headless_bootstrap index 485f5f8..6ba5a96 100755 --- a/overlay/usr/local/bin/headless_bootstrap +++ b/overlay/usr/local/bin/headless_bootstrap @@ -3,7 +3,7 @@ # SPDX-FileCopyrightText: Copyright 2022-2023, macmpi # SPDX-License-Identifier: MIT -HDLSBSTRP_VERSION="1.0" +HDLSBSTRP_VERSION="1.1" _apk() { local cmd="$1" @@ -29,7 +29,7 @@ _apk() { _preserve() { # create a back-up of element (file, folder, symlink) [ -z "${1}" ] && return 1 - [ -e "$1" ] && cp -a "$1" "${1}.orig" + [ -e "${1}" ] && cp -a "${1}" "${1}".orig } _restore() { @@ -37,7 +37,7 @@ _restore() { # previous back-up if available [ -z "${1}" ] && return 1 rm -rf "${1}" - [ -e "${1}.orig" ] && mv -f "${1}.orig" "${1}" + [ -e "${1}".orig ] && mv -f "${1}".orig "${1}" } # shellcheck disable=SC2142 # known special case @@ -69,7 +69,7 @@ cat <<-EOF >> /tmp/.trash/headless_cleanup rm -f /usr/local/bin/headless_bootstrap # Run unattended script if available - install -m755 "${ovlpath}/unattended.sh" /tmp/headless_unattended > /dev/null 2>&1 && \ + install -m755 "${ovlpath}"/unattended.sh /tmp/headless_unattended > /dev/null 2>&1 && \ _logger "Starting headless_unattended service" && \ rc-service headless_unattended start @@ -99,7 +99,7 @@ cat <<-EOF > /etc/ssh/sshd_config EOF # Client authorized_keys or no authentication -if install -m600 "${ovlpath}/authorized_keys" /tmp/.trash/authorized_keys > /dev/null 2>&1; then +if install -m600 "${ovlpath}"/authorized_keys /tmp/.trash/authorized_keys > /dev/null 2>&1; then _logger "Enabling public key SSH authentication..." cat <<-EOF >> /etc/ssh/sshd_config AuthenticationMethods publickey @@ -161,58 +161,66 @@ _logger "Internet access: $status" _setup_networking() { ## Setup Network interfaces -if [ -d "/sys/class/net/wlan0" ] && [ -f "${ovlpath}/wpa_supplicant.conf" ]; then +local has_wifi +_has_wifi() { return "$has_wifi"; } + +find /sys/class/ieee80211/*/device/net/* -maxdepth 0 -type d -exec basename {} \; > /tmp/.wlan_list 2>/dev/null +[ -s /tmp/.wlan_list ] && [ -f "${ovlpath}"/wpa_supplicant.conf ] +has_wifi=$? +if _has_wifi; then _logger "Configuring wifi..." _apk add wpa_supplicant _preserve "/etc/wpa_supplicant/wpa_supplicant.conf" - install -m600 "${ovlpath}/wpa_supplicant.conf" /etc/wpa_supplicant/wpa_supplicant.conf + install -m600 "${ovlpath}"/wpa_supplicant.conf /etc/wpa_supplicant/wpa_supplicant.conf + rc-service wpa_supplicant restart else - _logger "No wifi interface or setup file supplied" + _logger "No wifi interface or SSID/pass file supplied" fi _preserve "/etc/network/interfaces" -if ! install -m644 "${ovlpath}/interfaces" /etc/network/interfaces > /dev/null 2>&1; then - # set default interfaces if not specified by interface file on boot storage +if ! install -m644 "${ovlpath}"/interfaces /etc/network/interfaces > /dev/null 2>&1; then _logger "No interfaces file supplied, building defaults..." + cat <<-EOF > /etc/network/interfaces + auto lo + iface lo inet loopback + + EOF for dev in /sys/class/net/*; do - dev="$(basename "$dev")" - case ${dev%%[0-9]*} in + # shellcheck disable=SC2034 # Unused IFINDEX while still sourced from uevent + local DEVTYPE INTERFACE IFINDEX + DEVTYPE="" + # shellcheck source=/dev/null + . "$dev"/uevent + case ${INTERFACE%%[0-9]*} in lo) - cat <<-EOF >> /etc/network/interfaces - auto $dev - iface $dev inet loopback - - EOF - ;; + ;; eth) + cat <<-EOF >> /etc/network/interfaces + auto $INTERFACE + iface $INTERFACE inet dhcp + + EOF + ;; + *) + _has_wifi && grep -q "$INTERFACE" /tmp/.wlan_list && \ cat <<-EOF >> /etc/network/interfaces - auto $dev - iface $dev inet dhcp + auto $INTERFACE + iface $INTERFACE inet dhcp - EOF - ;; - wlan) - [ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && cat <<-EOF >> /etc/network/interfaces - auto $dev - iface $dev inet dhcp + EOF + [ "$DEVTYPE" = "gadget" ] && \ + cat <<-EOF >> /etc/network/interfaces && cat <<-EOF > /etc/resolv.conf + auto $INTERFACE + iface $INTERFACE inet static + address 10.42.0.2/24 + gateway 10.42.0.1 - EOF - ;; - usb) - cat <<-EOF >> /etc/network/interfaces - auto $dev - iface $dev inet static - address 10.42.0.2/24 - gateway 10.42.0.1 + EOF + nameserver 208.67.222.222 + nameserver 208.67.220.220 - EOF - - cat <<-EOF > /etc/resolv.conf - nameserver 208.67.222.222 - nameserver 208.67.220.220 - - EOF - ;; + EOF + ;; esac done fi @@ -226,25 +234,31 @@ _preserve "/etc/hostname" echo "alpine-headless" > /etc/hostname hostname -F /etc/hostname -grep -q "wlan" /etc/network/interfaces && \ - [ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && \ - rc-service wpa_supplicant restart rc-service networking restart +rm -f /tmp/.wlan_list } _setup_gadget() { -# load composite USB Serial/USB Ethernel driver & setup terminal +## load composite USB Serial/USB Ethernel driver & setup terminal _logger "Enabling USB-gadget Serial and Ethernet ports" lsmod | grep -q "dwc2" || modprobe -qs dwc2 -modprobe -qs g_cdc -# default config: xon/xoff flow control -stty -g -F /dev/ttyGS0 >/dev/null 2>&1 && setconsole /dev/ttyGS0 +# remove conflicting modules in case they were initially loaded (cmdline.txt) +modprobe -rq g_serial g_ether g_cdc +modprobe -q g_cdc && sleep 1 +# once driver has settled check if cable is connected: unload if not +[ "$( cat "$udc_gadget"/current_speed )" = "UNKNOWN" ] && \ + _logger "USB cable not connected !!" && modprobe -rq g_cdc && return 1 + +# default serial config: xon/xoff flow control +stty -g -F /dev/ttyGS0 >/dev/null 2>&1 # notes to users willing to connect from Linux Ubuntu-based host terminal: # - user on host needs to be part of dialout group (reboot required), and # - disable spurious AT commands from ModemManager on host-side Gadget serial port # you may create a /etc/udev/rules.d/99-ttyacms-gadget.rules as per: # https://linux-tips.com/t/prevent-modem-manager-to-capture-usb-serial-devices/284/2 # ATTRS{idVendor}=="0525" ATTRS{idProduct}=="a4aa", ENV{ID_MM_DEVICE_IGNORE}="1" + +setconsole /dev/ttyGS0 } @@ -258,9 +272,10 @@ _logger "Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi" # help randomness for wpa_supplicant and sshd (urandom until 3.16) rc-service seedrng restart || rc-service urandom restart -# setup USB gadget mode if device has compatible device-tree -find /proc/device-tree/soc/usb* -name "dr_mode" -print0 | \ - xargs -0 grep -q "peripheral" && _setup_gadget +# setup USB gadget mode if such device mode is enabled +udc_gadget="$( dirname "$( find -L /sys/class/udc/* -maxdepth 2 -type f -name "is_a_peripheral" 2>/dev/null)" )" +[ "$( cat "$udc_gadget"/is_a_peripheral 2>/dev/null )" = "0" ] && \ + _setup_gadget # Determine ovl file location # grab used ovl filename from dmesg @@ -293,10 +308,10 @@ _setup_networking # Test latest available version online # Can be skipped by creating a 'opt-out'-named dummy file aside apkovl file -[ -f "${ovlpath}/opt-out" ] || _tst_version & +[ -f "${ovlpath}"/opt-out ] || _tst_version & # setup sshd unless unattended.sh script prevents it -grep -q "^#NO_SSH$" "${ovlpath}/unattended.sh" > /dev/null 2>&1 \ +grep -q "^#NO_SSH$" "${ovlpath}"/unattended.sh > /dev/null 2>&1 \ || _setup_sshd _prep_cleanup