Compare commits

..

6 Commits
v1.0 ... main

Author SHA1 Message Date
macmpi bba42717fa
Merge pull request #24 from macmpi/dev
doc update
2023-12-06 09:09:33 +01:00
macmpi 7445b19996
doc update 2023-12-06 09:08:21 +01:00
macmpi f35a866875
Merge pull request #22 from macmpi/dev
version 1.2
2023-11-24 10:48:50 +01:00
macmpi 2046d877eb version 1.2
New feature: auto-update apkovl file with latest release on master
gadget: improved setup if several ports available and/or some unconnected
wlan: only install & start wpa_supplicant if needed
updated doc
2023-11-24 10:44:12 +01:00
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
7 changed files with 252 additions and 143 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!).\
@ -25,15 +25,19 @@ Extra files may be added next to `headless.apkovl.tar.gz` to customise boostrapp
- `interfaces`[^3] (*optional*): define network interfaces at will, if defaults DCHP-based are not suitable. - `interfaces`[^3] (*optional*): define network interfaces at will, if defaults DCHP-based are not suitable.
- `authorized_keys` (*optional*): provide client's public SSH key to secure `root` ssh login. - `authorized_keys` (*optional*): provide client's public SSH key to secure `root` ssh login.
- `ssh_host_*_key*` (*optional*): provide server's custom ssh keys to be injected (may be stored), instead of using bundled ones[^2] (not stored). Providing an empty key file will trigger new keys generation (ssh server may take longer to start). - `ssh_host_*_key*` (*optional*): provide server's custom ssh keys to be injected (may be stored), instead of using bundled ones[^2] (not stored). Providing an empty key file will trigger new keys generation (ssh server may take longer to start).
- `opt-out` (*optional*): dummy file to opt-out internet features (connection status, version check, auto-update) and related links usage anonymous [telemetry](https://is.gd/privacy.php).
- `auto-updt` (*optional*): allow apkovl file automatic update with latest from master branch. If it contains *reboot* keyword all in one line, system will reboot after succesful update (unless ssh session is active or `unattended.sh` script is available).
**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`).\
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`.
## Goody:
Seamless USB-serial & USB-ethernet gadget mode (*e.g. PiZero*):
- Make sure dwc2/dwc3 driver is loaded accordingly, and device configuration is set to `peripheral` mode: this may be hardware (including cable) and/or software driven.\
(on supporting Pi devices, just add `dtoverlay=dwc2,dr_mode=peripheral` in `usercfg.txt` (or `config.txt`) to force by software)
- Plug USB cable into host Computer port before boot.\
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`.
[^1]: Initial boot fully preserves system's original state (config files & installed packages): a fresh system will therefore come-up as unconfigured. [^1]: Initial boot fully preserves system's original state (config files & installed packages): a fresh system will therefore come-up as unconfigured.
[^2]: About bundled ssh keys: this overlay is meant to **quickly bootstrap** system in order to then proceed with proper install; therefore it purposely embeds [some ssh keys](https://github.com/macmpi/alpine-linux-headless-bootstrap/tree/main/overlay/tmp/.trash) so that bootstrapping is as fast as possible. Those temporary keys are moved in RAM /tmp: they will **not be stored/reused** once actual system install is performed (whether or not ssh server is installed in final setup). [^2]: About bundled ssh keys: this overlay is meant to **quickly bootstrap** system in order to then proceed with proper install; therefore it purposely embeds [some ssh keys](https://github.com/macmpi/alpine-linux-headless-bootstrap/tree/main/overlay/tmp/.trash) so that bootstrapping is as fast as possible. Those temporary keys are moved in RAM /tmp: they will **not be stored/reused** once actual system install is performed (whether or not ssh server is installed in final setup).
@ -43,8 +47,9 @@ 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_bootstrap`](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.

View File

@ -0,0 +1 @@
36892e9aa76807941602160ff62948c8953eefa36f9b386a0c7302fc46710b473a3e14b7eaf223a1ae6ac6454a839c78049c311fdbd50a204b2a011cb8faa474 headless.apkovl.tar.gz

35
make.sh
View File

@ -3,28 +3,35 @@
# 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
sha512sum headless.apkovl.tar.gz > headless.apkovl.tar.gz.sha512
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.2"
_apk() { _apk() {
local cmd="$1" local cmd="$1"
@ -12,11 +12,11 @@ _apk() {
case $cmd in case $cmd in
add) # install only if not already present add) # install only if not already present
if ! apk info | grep -wq "${pkg}"; then if ! apk info | grep -wq "${pkg}"; then
apk add "$pkg" && printf '%s ' "${pkg}" >> /tmp/.trash/installed apk add "$pkg" && printf '%s ' "${pkg}" >>/tmp/.trash/installed
fi fi
;; ;;
del) # delete only if previously installed del) # delete only if previously installed
if grep -wq "$pkg" /tmp/.trash/installed > /dev/null 2>&1; then if grep -wq "$pkg" /tmp/.trash/installed >/dev/null 2>&1; then
apk del "$pkg" && sed -i 's/\b'"${pkg}"'\b//' /tmp/.trash/installed apk del "$pkg" && sed -i 's/\b'"${pkg}"'\b//' /tmp/.trash/installed
fi fi
;; ;;
@ -27,17 +27,17 @@ _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() {
# remove element (file, folder, symlink) and replace by # Remove element (file, folder, symlink) and replace by
# 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
@ -46,11 +46,11 @@ alias _logger='logger -st "${0##*/}"'
##### End of part to be duplicated into headless_cleanup (do not alter!) ##### End of part to be duplicated into headless_cleanup (do not alter!)
_prep_cleanup() { _prep_cleanup() {
## Prep for final headless_cleanup ## Prep for final headless_cleanup:
# clears any installed packages and settings # clears any installed packages and settings.
# copy begininng of this file to keep functions # Copy begininng of this file to keep functions.
sed -n '/^#* End .*alter!)$/q;p' /usr/local/bin/headless_bootstrap > /tmp/.trash/headless_cleanup sed -n '/^#* End .*alter!)$/q;p' /usr/local/bin/headless_bootstrap >/tmp/.trash/headless_cleanup
cat <<-EOF >> /tmp/.trash/headless_cleanup cat <<-EOF >>/tmp/.trash/headless_cleanup
# Redirect stdout and errors to console as service won't show messages # Redirect stdout and errors to console as service won't show messages
exec 1>/dev/console 2>&1 exec 1>/dev/console 2>&1
@ -64,44 +64,45 @@ cat <<-EOF >> /tmp/.trash/headless_cleanup
_restore "/etc/hostname" _restore "/etc/hostname"
rm -f /etc/modprobe.d/headless_gadget.conf rm -f /etc/modprobe.d/headless_gadget.conf
# remove from boot service to avoid spurious openrc recalls from unattended script # Remove from boot service to avoid spurious openrc recalls from unattended script.
rm -f /etc/runlevels/default/headless_bootstrap rm -f /etc/runlevels/default/headless_bootstrap
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
rm -f /etc/init.d/headless_* rm -f /etc/init.d/headless_*
_logger "Clean-up done, enjoy !" _logger "Clean-up done, enjoy !"
cat /tmp/.trash/banner > /dev/console cat /tmp/.trash/banner >/dev/console
if [ -c /dev/ttyGS0 ]; then if [ -c /dev/ttyGS${gdgt_id} ]; then
# Enabling terminal login into ttyGS0 serial for 60 sec # Enabling terminal login into valid serial port:
# no choice than making permanent change to pre 3.19 versions of /etc/securetty # no choice than making permanent change to /etc/securetty (Alpine 3.19 already has ttyGS0).
grep -q "ttyGS0" /etc/securetty || echo "ttyGS0" >> /etc/securetty grep -q "ttyGS${gdgt_id}" /etc/securetty || echo "ttyGS${gdgt_id}" >>/etc/securetty
/sbin/getty -L 115200 ttyGS0 vt100 & /sbin/getty -L 115200 /dev/ttyGS${gdgt_id} vt100 &
fi fi
exit 0
EOF EOF
chmod +x /tmp/.trash/headless_cleanup chmod +x /tmp/.trash/headless_cleanup
} }
_setup_sshd() { _setup_sshd() {
## Setup temporary SSH server (root login, no password) ## Setup temporary SSH server (root login, no password):
# We use some bundled (or optionaly provided) keys to avoid generation at startup and save time # we use some bundled (or optionaly provided) keys to avoid generation at startup and save time.
_apk add openssh-server _apk add openssh-server
_preserve "/etc/ssh/sshd_config" _preserve "/etc/ssh/sshd_config"
_preserve "/etc/conf.d/sshd" _preserve "/etc/conf.d/sshd"
cat <<-EOF > /etc/ssh/sshd_config cat <<-EOF >/etc/ssh/sshd_config
PermitRootLogin yes PermitRootLogin yes
Banner /tmp/.trash/banner Banner /tmp/.trash/banner
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
AuthorizedKeysFile /tmp/.trash/authorized_keys AuthorizedKeysFile /tmp/.trash/authorized_keys
# relax strict mode as authorized_keys are inside /tmp # relax strict mode as authorized_keys are inside /tmp
@ -109,7 +110,7 @@ if install -m600 "${ovlpath}/authorized_keys" /tmp/.trash/authorized_keys > /dev
EOF EOF
else else
_logger "No SSH authentication." _logger "No SSH authentication."
cat <<-EOF >> /etc/ssh/sshd_config cat <<-EOF >>/etc/ssh/sshd_config
AuthenticationMethods none AuthenticationMethods none
PermitEmptyPasswords yes PermitEmptyPasswords yes
EOF EOF
@ -118,8 +119,8 @@ fi
# Server keys: inject optional custom keys, or generate new (might be stored), # Server keys: inject optional custom keys, or generate new (might be stored),
# or use bundeled ones (not stored) # or use bundeled ones (not stored)
local keygen_stance="sshd_disable_keygen=yes" local keygen_stance="sshd_disable_keygen=yes"
if install -m600 "${ovlpath}"/ssh_host_*_key* /etc/ssh/ > /dev/null 2>&1; then if install -m600 "${ovlpath}"/ssh_host_*_key* /etc/ssh/ >/dev/null 2>&1; then
# check for empty key within injected ones: if found, generate new keys # Check for empty key within injected ones: if found, generate new keys.
if find /etc/ssh/ -maxdepth 1 -type f -name 'ssh_host_*_key*' -empty | grep -q .; then if find /etc/ssh/ -maxdepth 1 -type f -name 'ssh_host_*_key*' -empty | grep -q .; then
rm /etc/ssh/ssh_host_*_key* rm /etc/ssh/ssh_host_*_key*
keygen_stance="" keygen_stance=""
@ -130,89 +131,143 @@ if install -m600 "${ovlpath}"/ssh_host_*_key* /etc/ssh/ > /dev/null 2>&1; then
fi fi
else else
_logger "Using bundled ssh keys from RAM..." _logger "Using bundled ssh keys from RAM..."
cat <<-EOF >> /etc/ssh/sshd_config cat <<-EOF >>/etc/ssh/sshd_config
HostKey /tmp/.trash/ssh_host_ed25519_key HostKey /tmp/.trash/ssh_host_ed25519_key
HostKey /tmp/.trash/ssh_host_rsa_key HostKey /tmp/.trash/ssh_host_rsa_key
EOF EOF
fi fi
echo "$keygen_stance" >> /etc/conf.d/sshd echo "$keygen_stance" >>/etc/conf.d/sshd
rc-service sshd restart rc-service sshd restart
} }
_updt_apkovl() {
## Update apkovl overlay file & eventually reboot
# URL redirects to apkovl file on github master: is.gd shortener provides basic analytics.
# Analytics are public and can be checked at https://is.gd/stats.php?url=apkovl_master
# Privacy policy: https://is.gd/privacy.php
local file_url="https://is.gd/apkovl_master"
local sha_url="https://github.com/macmpi/alpine-linux-headless-bootstrap/raw/main/headless.apkovl.tar.gz.sha512"
local updt_status="failed, keeping original version"
if wget -q -O /tmp/apkovl -T 10 "$file_url" >/dev/null 2>&1 && \
wget -q -O /tmp/sha -T 10 "$sha_url" >/dev/null 2>&1 && \
[ "$( sha512sum /tmp/apkovl | awk '{print $1}' )" = "$( awk '{print $1}' /tmp/sha )" ]; then
_is_ro && mount -o remount,rw "${ovlpath}"
cp /tmp/apkovl "${ovl}"
_is_ro && mount -o remount,ro "${ovlpath}"
! [ "$( sha512sum "${ovl}" | awk '{print $1}' )" = "$( awk '{print $1}' /tmp/sha )" ] && \
_logger "Bad update: original apkovl file may be altered, please check!..." && return 1
updt_status="successful"
fi
rm -f /tmp/apkovl /tmp/sha
_logger "Update $updt_status"
if [ "$updt_status" = "successful" ]; then
printf '%s\n\n' "Updated" >>/tmp/.trash/banner
else
printf '\n' >>/tmp/.trash/banner
return 1
fi
# Reboot if specified in auto-updt file (and no ssh session ongoing nor unattended.sh script available).
! pgrep -a -P "$( cat /run/sshd.pid 2>/dev/null )" 2>/dev/null | grep -q "sshd: root@pts" && \
! [ -f "${ovlpath}"/unattended.sh ] && \
grep -q "^reboot$" "${ovlpath}"/auto-updt && \
_logger "Will reboot in 3sec..." && sleep 3 && reboot
exit 0
}
_tst_version() { _tst_version() {
# Tested URL redirects to github project page: is.gd shortener provides basic analytics. ## Compare current version with latest online, notify & eventally calls for update
# URL redirects to github project page: is.gd shortener provides basic analytics.
# Analytics are public and can be checked at https://is.gd/stats.php?url=apkovl_run # Analytics are public and can be checked at https://is.gd/stats.php?url=apkovl_run
# Privacy policy: https://is.gd/privacy.php # Privacy policy: https://is.gd/privacy.php
local new_vers="" local vers=""
local status="failed"
local ref="/macmpi/alpine-linux-headless-bootstrap/releases/tag/v" local ref="/macmpi/alpine-linux-headless-bootstrap/releases/tag/v"
if wget -q -O /tmp/homepg -T 10 https://is.gd/apkovl_run > /dev/null 2>&1; then local url="https://is.gd/apkovl_run"
status="success"
ver="$( grep -o "$ref.*\"" /tmp/homepg | grep -Eo '[0-9]+[\.[0-9]+]*' )" if wget -q -O /tmp/homepg -T 10 "$url" >/dev/null 2>&1; then
_logger "Internet access: success"
vers="$( grep -o "$ref.*\"" /tmp/homepg | grep -Eo '[0-9]+[\.[0-9]+]*' )"
rm -f /tmp/homepg rm -f /tmp/homepg
[ -n "$ver" ] && ! [ "$ver" = "$HDLSBSTRP_VERSION" ] && \ if [ -n "$vers" ] && ! [ "$vers" = "$HDLSBSTRP_VERSION" ]; then
new_vers="!! Version $ver is available on Github project page !!" && \ vers="!! Version $vers is available on Github project page !!"
_logger "$new_vers" && \ _logger "$vers"
printf '%s\n\n' "$new_vers" >> /tmp/.trash/banner printf '%s\n' "$vers" >>/tmp/.trash/banner
# Optionally update apkovl if key-file allows it.
if [ -f "${ovlpath}"/auto-updt ]; then
_logger "Updating overlay file..."
_updt_apkovl &
else
_logger "(check doc to enable auto-update)"
printf '%s\n\n' "(check doc to enable auto-update)" >>/tmp/.trash/banner
fi
fi
else
_logger "Internet access: failed"
fi fi
_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 wlan_lst
_logger "Configuring wifi..." _has_wifi() { return "$has_wifi"; }
_apk add wpa_supplicant
_preserve "/etc/wpa_supplicant/wpa_supplicant.conf" wlan_lst="$( find /sys/class/net/*/phy80211 -exec \
install -m600 "${ovlpath}/wpa_supplicant.conf" /etc/wpa_supplicant/wpa_supplicant.conf sh -c 'printf %s\| "$( basename "$( dirname "$0" )" )"' {} \; 2>/dev/null )"
else wlan_lst="${wlan_lst%\|}"
_logger "No wifi interface or setup file supplied" [ -n "$wlan_lst" ] && [ -f "${ovlpath}"/wpa_supplicant.conf ]
fi has_wifi=$?
_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 cat <<-EOF >>/etc/network/interfaces
auto $dev auto $INTERFACE
iface $dev inet dhcp iface $INTERFACE inet dhcp
EOF EOF
;; ;;
wlan) *)
[ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && cat <<-EOF >> /etc/network/interfaces # According to below we could rely on DEVTYPE for wlan devices
auto $dev # https://lists.freedesktop.org/archives/systemd-devel/2014-January/015999.html
iface $dev inet dhcp # but...some wlan might still be ill-behaved: use wlan_lst
# shellcheck disable=SC2169 # ash does support string replacement.
_has_wifi && ! [ "${wlan_lst/$INTERFACE/}" = "$wlan_lst" ] && \
cat <<-EOF >>/etc/network/interfaces
auto $INTERFACE
iface $INTERFACE inet dhcp
EOF EOF
;; # Ensure considered gadget interface is actually the connected one (may have several).
usb) [ "$DEVTYPE" = "gadget" ] && \
cat <<-EOF >> /etc/network/interfaces find /sys/class/udc/*/device/gadget."${gdgt_id}"/net/"$INTERFACE" -maxdepth 0 >/dev/null 2>&1 && \
auto $dev cat <<-EOF >>/etc/network/interfaces && cat <<-EOF >/etc/resolv.conf
iface $dev inet static auto $INTERFACE
address 10.42.0.2/24 iface $INTERFACE inet static
gateway 10.42.0.1 address 10.42.0.2/24
gateway 10.42.0.1
EOF EOF
nameserver 208.67.222.222
nameserver 208.67.220.220
cat <<-EOF > /etc/resolv.conf EOF
nameserver 208.67.222.222 ;;
nameserver 208.67.220.220
EOF
;;
esac esac
done done
fi fi
@ -222,29 +277,53 @@ echo "Using following network interfaces:"
cat /etc/network/interfaces cat /etc/network/interfaces
echo "###################################" echo "###################################"
if _has_wifi && grep -qE "$wlan_lst" /etc/network/interfaces; 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
rc-service wpa_supplicant restart
else
_logger "No wifi interface or SSID/pass file supplied"
fi
_preserve "/etc/hostname" _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
} }
_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 # Remove conflicting modules in case they were initially loaded (cmdline.txt).
modprobe -qs g_cdc modprobe -r g_serial g_ether g_cdc
# default config: xon/xoff flow control modprobe g_cdc
stty -g -F /dev/ttyGS0 >/dev/null 2>&1 && setconsole /dev/ttyGS0 # Wait for g_cdc to settle and serial ports become available
# notes to users willing to connect from Linux Ubuntu-based host terminal: timeout 1 sh <<-EOF
# - user on host needs to be part of dialout group (reboot required), and while ! grep -q "ttyGS" /proc/devices; do sleep 0.2; done
# - disable spurious AT commands from ModemManager on host-side Gadget serial port EOF
# 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 # Determine which gadget ID is connected with USB cable (assume just one max).
# ATTRS{idVendor}=="0525" ATTRS{idProduct}=="a4aa", ENV{ID_MM_DEVICE_IGNORE}="1" # (setting console to unconnected serial port would block boot)
gdgt_id="$( find /sys/class/udc/*/current_speed -exec \
sh -c 'grep -vq "UNKNOWN" "$0" && find ${0/current_speed/}device/gadget.* -maxdepth 0' {} \; \
| sed 's/\/.*gadget\.//' )"
if [ -c /dev/ttyGS"${gdgt_id}" ]; then
# Default serial config: xon/xoff flow control.
stty -F /dev/ttyGS"${gdgt_id}"
setconsole /dev/ttyGS"${gdgt_id}"
# 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
# one 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"
else
_logger "USB-gadget port not connected !"
modprobe -r g_cdc
fi
} }
@ -255,30 +334,35 @@ stty -g -F /dev/ttyGS0 >/dev/null 2>&1 && setconsole /dev/ttyGS0
exec 1>/dev/console 2>&1 exec 1>/dev/console 2>&1
_logger "Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi" _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 ports if some ports are enabled in peripheral mode.
find /proc/device-tree/soc/usb* -name "dr_mode" -print0 | \ # Note: we assume dwc2/dwc3 is pre-loaded, we just check mode.
xargs -0 grep -q "peripheral" && _setup_gadget gdgt_id=""
find /sys/class/udc/*/is_a_peripheral -print0 2>/dev/null | \
xargs -0 cat 2>/dev/null | grep -q "0" && \
_setup_gadget
# Determine ovl file location # Determine ovl file location.
# grab used ovl filename from dmesg # Grab used ovl filename from dmesg.
ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )" ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )"
if [ -f "${ovl}" ]; then if [ -f "${ovl}" ]; then
ovlpath="$( dirname "$ovl" )" ovlpath="$( dirname "$ovl" )"
else else
# search path again as mountpoint have been changed later in the boot process... # Search path again as mountpoint have been changed later in the boot process...
ovl="$( basename "${ovl}" )" ovl="$( basename "${ovl}" )"
ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 ) ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 )
ovl="${ovlpath}/${ovl}" ovl="${ovlpath}/${ovl}"
fi fi
# Create banner file # Create banner file.
warn="" warn=""
grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; RO=$? grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; is_ro=$?
[ "$RO" -eq "0" ] && warn="(remount partition rw!)" _is_ro() { return "$is_ro"; }
cat <<-EOF > /tmp/.trash/banner
_is_ro && warn="(remount partition rw!)"
cat <<-EOF >/tmp/.trash/banner
Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi
@ -291,12 +375,12 @@ cat <<-EOF > /tmp/.trash/banner
_setup_networking _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

10
sample_auto-updt Normal file
View File

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT
# Automated reboot after update is cancelled if:
# - there is an opened ssh session
# - unattended.sh script is provided
# Uncomment line below to enable reboot after update
#reboot

View File

@ -37,10 +37,11 @@ else
fi fi
# also works in case volume is mounted read-only # also works in case volume is mounted read-only
grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; RO=$? grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; is_ro=$?
[ "$RO" -eq "0" ] && mount -o remount,rw "${ovlpath}" _is_ro() { return "$is_ro"; }
_is_ro && mount -o remount,rw "${ovlpath}"
rm -f "${ovl}" rm -f "${ovl}"
[ "$RO" -eq "0" ] && mount -o remount,ro "${ovlpath}" _is_ro && mount -o remount,ro "${ovlpath}"
######################################################## ########################################################
@ -98,6 +99,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