Compare commits

..

2 Commits

Author SHA1 Message Date
macmpi 0d3694172a
Merge pull request #21 from macmpi/dev
version 1.1
2023-11-17 10:11:29 +01:00
macmpi 4026274721 version 1.1
improved interfaces detection: fixes https://github.com/macmpi/alpine-linux-headless-bootstrap/issues/20
make.sh: tighten-up archive construction (reproducibility)
2023-11-17 10:09:14 +01:00
5 changed files with 94 additions and 71 deletions

View File

@ -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). 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.\ 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@<IP>`\ 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@<IP>`\
(system IP address may be determined with any IP scanning tools such as `nmap`). (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!).\ 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):\ **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.\ 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`. 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`. Main execution steps are logged: `cat /var/log/messages | grep headless`.
@ -44,7 +44,8 @@ Main execution steps are logged: `cat /var/log/messages | grep headless`.
## Want to tweak more ? ## Want to tweak more ?
This repository may be forked/cloned/downloaded.\ This repository may be forked/cloned/downloaded.\
Main script file is [`headless.start`](https://github.com/macmpi/alpine-linux-headless-bootstrap/tree/main/overlay/usr/local/bin/headless_bootstrap).\ Main script file is [`headless.start`](https://github.com/macmpi/alpine-linux-headless-bootstrap/tree/main/overlay/usr/local/bin/headless_bootstrap).\
Execute `./make.sh` to rebuild `headless.apkovl.tar.gz` after changes. Execute `./make.sh` to rebuild `headless.apkovl.tar.gz` after changes.\
(requires `busybox`; check `busybox` build options if not running from Alpine or Ubuntu)
## Credits ## Credits

Binary file not shown.

34
make.sh
View File

@ -3,28 +3,34 @@
# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi # SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# script meant to be run on Alpine (busybox) or on Ubuntu
# verify busybox build options if eventually using other platforms
command -v doas > /dev/null || alias doas="/usr/bin/sudo" command -v doas > /dev/null || alias doas="/usr/bin/sudo"
build_path="$(mktemp -d)" build_path="$(mktemp -d)"
if [ -n "$build_path" ]; then if [ -n "$build_path" ]; then
cp -r overlay "$build_path"/. # prefer timestamp option for touch as it works on directories too
find "$build_path"/overlay/ -exec touch -md "$(date '+%F 00:00:00')" {} \; t_stamp="$( TZ=UTC date +%Y%m%d0000.00 )"
cp -a overlay "$build_path"/.
# setting owner/groups for runtime (won't affect mtime) find "$build_path"/overlay/ -exec sh -c 'TZ=UTC touch -chm -t "$0" "$1"' "$t_stamp" {} \;
# setting modes and owner/groups for runtime (won't affect mtime)
find "$build_path"/overlay/etc -type d -exec chmod 755 {} \; find "$build_path"/overlay/etc -type d -exec chmod 755 {} \;
chmod +x "$build_path"/overlay/etc/init.d/* chmod 755 "$build_path"/overlay/etc/init.d/*
find "$build_path"/overlay/usr -type d -exec chmod 755 {} \; chmod 755 "$build_path"/overlay/etc/runlevels/default/*
chmod +x "$build_path"/overlay/usr/local/bin/*
chmod 777 "$build_path"/overlay/tmp chmod 777 "$build_path"/overlay/tmp
chmod 700 "$build_path"/overlay/tmp/.trash chmod 700 "$build_path"/overlay/tmp/.trash
chmod 600 "$build_path"/overlay/tmp/.trash/ssh_host_*_key chmod -R 600 "$build_path"/overlay/tmp/.trash/ssh_host_*_key
doas chown -R 0:0 "$build_path"/overlay/* find "$build_path"/overlay/usr -type d -exec chmod 755 {} \;
chmod 755 "$build_path"/overlay/usr/local/bin/*
doas tar -cvf "$build_path"/headless.apkovl.tar -C "$build_path"/overlay etc usr tmp doas chown -Rh 0:0 "$build_path"/overlay/*
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
# busybox config on Alpine & Ubuntu has FEATURE_TAR_GNU_EXTENSIONS
# (will preserve user/group/modes & mtime) and FEATURE_TAR_LONG_OPTIONS
# 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 "$t_stamp" headless.apkovl.tar.gz
doas rm -rf "$build_path" doas rm -rf "$build_path"
fi fi

View File

@ -3,7 +3,7 @@
# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi # SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
HDLSBSTRP_VERSION="1.0" HDLSBSTRP_VERSION="1.1"
_apk() { _apk() {
local cmd="$1" local cmd="$1"
@ -29,7 +29,7 @@ _apk() {
_preserve() { _preserve() {
# create a back-up of element (file, folder, symlink) # create a back-up of element (file, folder, symlink)
[ -z "${1}" ] && return 1 [ -z "${1}" ] && return 1
[ -e "$1" ] && cp -a "$1" "${1}.orig" [ -e "${1}" ] && cp -a "${1}" "${1}".orig
} }
_restore() { _restore() {
@ -37,7 +37,7 @@ _restore() {
# previous back-up if available # previous back-up if available
[ -z "${1}" ] && return 1 [ -z "${1}" ] && return 1
rm -rf "${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 # shellcheck disable=SC2142 # known special case
@ -69,7 +69,7 @@ cat <<-EOF >> /tmp/.trash/headless_cleanup
rm -f /usr/local/bin/headless_bootstrap rm -f /usr/local/bin/headless_bootstrap
# Run unattended script if available # 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" && \ _logger "Starting headless_unattended service" && \
rc-service headless_unattended start rc-service headless_unattended start
@ -99,7 +99,7 @@ cat <<-EOF > /etc/ssh/sshd_config
EOF EOF
# Client authorized_keys or no authentication # 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..." _logger "Enabling public key SSH authentication..."
cat <<-EOF >> /etc/ssh/sshd_config cat <<-EOF >> /etc/ssh/sshd_config
AuthenticationMethods publickey AuthenticationMethods publickey
@ -161,58 +161,66 @@ _logger "Internet access: $status"
_setup_networking() { _setup_networking() {
## Setup Network interfaces ## 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..." _logger "Configuring wifi..."
_apk add wpa_supplicant _apk add wpa_supplicant
_preserve "/etc/wpa_supplicant/wpa_supplicant.conf" _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 else
_logger "No wifi interface or setup file supplied" _logger "No wifi interface or SSID/pass file supplied"
fi fi
_preserve "/etc/network/interfaces" _preserve "/etc/network/interfaces"
if ! install -m644 "${ovlpath}/interfaces" /etc/network/interfaces > /dev/null 2>&1; then 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
_logger "No interfaces file supplied, building defaults..." _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 for dev in /sys/class/net/*; do
dev="$(basename "$dev")" # shellcheck disable=SC2034 # Unused IFINDEX while still sourced from uevent
case ${dev%%[0-9]*} in local DEVTYPE INTERFACE IFINDEX
DEVTYPE=""
# shellcheck source=/dev/null
. "$dev"/uevent
case ${INTERFACE%%[0-9]*} in
lo) lo)
cat <<-EOF >> /etc/network/interfaces ;;
auto $dev
iface $dev inet loopback
EOF
;;
eth) 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 cat <<-EOF >> /etc/network/interfaces
auto $dev auto $INTERFACE
iface $dev inet dhcp iface $INTERFACE inet dhcp
EOF EOF
;; [ "$DEVTYPE" = "gadget" ] && \
wlan) cat <<-EOF >> /etc/network/interfaces && cat <<-EOF > /etc/resolv.conf
[ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && cat <<-EOF >> /etc/network/interfaces auto $INTERFACE
auto $dev iface $INTERFACE inet static
iface $dev inet dhcp address 10.42.0.2/24
gateway 10.42.0.1
EOF EOF
;; nameserver 208.67.222.222
usb) nameserver 208.67.220.220
cat <<-EOF >> /etc/network/interfaces
auto $dev
iface $dev inet static
address 10.42.0.2/24
gateway 10.42.0.1
EOF EOF
;;
cat <<-EOF > /etc/resolv.conf
nameserver 208.67.222.222
nameserver 208.67.220.220
EOF
;;
esac esac
done done
fi fi
@ -226,25 +234,31 @@ _preserve "/etc/hostname"
echo "alpine-headless" > /etc/hostname echo "alpine-headless" > /etc/hostname
hostname -F /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 rc-service networking restart
rm -f /tmp/.wlan_list
} }
_setup_gadget() { _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" _logger "Enabling USB-gadget Serial and Ethernet ports"
lsmod | grep -q "dwc2" || modprobe -qs dwc2 lsmod | grep -q "dwc2" || modprobe -qs dwc2
modprobe -qs g_cdc # remove conflicting modules in case they were initially loaded (cmdline.txt)
# default config: xon/xoff flow control modprobe -rq g_serial g_ether g_cdc
stty -g -F /dev/ttyGS0 >/dev/null 2>&1 && setconsole /dev/ttyGS0 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: # 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 # - 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 # - 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: # 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 # 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" # 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) # help randomness for wpa_supplicant and sshd (urandom until 3.16)
rc-service seedrng restart || rc-service urandom restart rc-service seedrng restart || rc-service urandom restart
# setup USB gadget mode if device has compatible device-tree # setup USB gadget mode if such device mode is enabled
find /proc/device-tree/soc/usb* -name "dr_mode" -print0 | \ udc_gadget="$( dirname "$( find -L /sys/class/udc/* -maxdepth 2 -type f -name "is_a_peripheral" 2>/dev/null)" )"
xargs -0 grep -q "peripheral" && _setup_gadget [ "$( cat "$udc_gadget"/is_a_peripheral 2>/dev/null )" = "0" ] && \
_setup_gadget
# Determine ovl file location # Determine ovl file location
# grab used ovl filename from dmesg # grab used ovl filename from dmesg
@ -293,10 +308,10 @@ _setup_networking
# Test latest available version online # Test latest available version online
# Can be skipped by creating a 'opt-out'-named dummy file aside apkovl file # 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 # 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 || _setup_sshd
_prep_cleanup _prep_cleanup

View File

@ -98,6 +98,7 @@ cat <<-EOF > /tmp/ANSWERFILE
# trick setup-alpine to pretend existing SSH connection # trick setup-alpine to pretend existing SSH connection
# and therefore keep (do not reset) network interfaces while running in background # and therefore keep (do not reset) network interfaces while running in background
# requires alpine-conf 3.15.1 and later, available from Alpine 3.17
SSH_CONNECTION="FAKE" setup-alpine -ef /tmp/ANSWERFILE SSH_CONNECTION="FAKE" setup-alpine -ef /tmp/ANSWERFILE
lbu commit -d lbu commit -d