Compare commits

...

2 Commits

Author SHA1 Message Date
macmpi 256b90bfbb
Merge pull request #19 from macmpi/dev
version 1.0
2023-11-13 22:01:00 +01:00
macmpi f430fc3ce5 version 1.0
unattended.sh script may disable sshd (#NO_SSH option)
gadget mode: enable both USB serial terminal and USB-ethernet networking
check new version availability online (notified on login banner & logs)
redesigned as OpenRC services
many other minor improvements
2023-11-13 21:34:41 +01:00
21 changed files with 419 additions and 297 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2022 macmpi Copyright (c) 2022-2023 macmpi
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -4,45 +4,46 @@
However, in many cases one might want to deploy a headless system that is only available through a network connection (ethernet, wifi or as USB ethernet gadget). However, in many cases one might want to deploy a headless system that is only available through a network connection (ethernet, wifi or as USB ethernet gadget).
This repo provides an **overlay file** to initially bootstrap[^1] a headless system (leveraging Alpine distro's `initramfs` feature): it starts a ssh server to log-into from another Computer, so that actual install on fresh system (or rescue on existing disk-based system) can then be performed remotely.\ This repo provides an **overlay file** to initially bootstrap[^1] a headless system (leveraging Alpine distro's `initramfs` feature): it starts a ssh server to log-into from another Computer, so that actual install on fresh system (or rescue on existing disk-based system) can then be performed remotely.\
An optional script may be launched at startup, to perform automated actions/setup. An optional script may also be launched during that same initial bootstrap, to perform fully automated setup.
## Setup procedure: ## Setup procedure:
Please follow [Alpine Linux Wiki](https://wiki.alpinelinux.org/wiki/Installation#Installation_Overview) to download & create installation media for the target platform.\ Please follow [Alpine Linux Wiki](https://wiki.alpinelinux.org/wiki/Installation#Installation_Overview) to download & create installation media for the target platform.\
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 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 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 file](#extra-configuration) 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 setup!).\ As with Alpine Linux initial bring-up, `root` account has no password initially (change that during target setup!).\
From there, actual system install can be performed as usual with `setup-alpine` for instance (check [wiki](https://wiki.alpinelinux.org/wiki/Alpine_setup_scripts#setup-alpine) for details). From there, actual system install can be performed as usual with `setup-alpine` for instance (check [wiki](https://wiki.alpinelinux.org/wiki/Alpine_setup_scripts#setup-alpine) for details).
## Extra configuration: ## Extra configuration:
Extra files may be added next to `headless.apkovl.tar.gz` to customise boostrapping configuration (check sample files): Extra files may be added next to `headless.apkovl.tar.gz` to customise boostrapping configuration (check sample files):
- `wpa_supplicant.conf`[^3] (*mandatory for wifi usecase*): define wifi SSID & password. - `wpa_supplicant.conf`[^3] (*mandatory for wifi usecase*): define wifi SSID & password.
- `unattended.sh`[^3] (*optional*): provide a deployment script to automate setup & customizations during initial bootstrap.
- `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).
- `unattended.sh`[^3] (*optional*): create custom automated deployment script to further tune & extend actual setup (backgrounded).
**Goody:** seamless USB-ethernet gadget boostrapping (PiZero for instance):\ **Goody:** seamless USB-serial & USB-ethernet gadget mode (PiZero for instance):\
On supporting Pi devices, just add `dtoverlay=dwc2` in `usercfg.txt` (or `config.txt`), and plug USB cable into 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.\
With Computer set-up to share networking with USB interface as 10.42.0.1 gateway, one can log into device from Computer with: `ssh root@10.42.0.2` 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 in `/var/log/messages`. Main execution steps are logged: `cat /var/log/messages | grep headless`.
[^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/etc/ssh) 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).
[^3]: These files are linux text files: Windows/macOS users need to use text editors supporting linux text line-ending (such as [notepad++](https://notepad-plus-plus.org/), BBEdit or any similar). [^3]: These files are linux text files: Windows/macOS users need to use text editors supporting linux text line-ending (such as [notepad++](https://notepad-plus-plus.org/), BBEdit or any similar).
## 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/blob/main/overlay/etc/local.d/headless.start).\ 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.

Binary file not shown.

32
make.sh
View File

@ -1,8 +1,30 @@
#!/bin/sh #!/bin/busybox sh
# Copyright 2022 - 2023, macmpi # SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
chmod 600 overlay/etc/ssh/ssh_host_*_key
chmod +x overlay/etc/local.d/headless.start command -v doas > /dev/null || alias doas="/usr/bin/sudo"
tar czvf headless.apkovl.tar.gz -C overlay etc --owner=0 --group=0
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)
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 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
doas rm -rf "$build_path"
fi

View File

@ -0,0 +1,12 @@
#!/sbin/openrc-run
# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT
description="Headless main boostrappring script"
name="Headless bootstrap"
command="/usr/local/bin/headless_bootstrap"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"

View File

@ -0,0 +1,12 @@
#!/sbin/openrc-run
# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT
description="Headless cleanup script"
name="Headless cleanup"
command="/tmp/.trash/headless_cleanup"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"

View File

@ -0,0 +1,12 @@
#!/sbin/openrc-run
# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT
description="Headless unattended setup script (optional)"
name="Headless unattended"
command="/tmp/headless_unattended"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"

View File

@ -1,247 +0,0 @@
#!/bin/sh
# Copyright 2022 - 2023, macmpi
# SPDX-License-Identifier: MIT
VERSION="0.9"
_apk() {
local cmd="$1"
local pkg="$2"
case $cmd in
add) # install only if not already present
if ! apk info | grep -wq "${pkg}"; then
apk add "$pkg" && printf "${pkg} " >> /tmp/.trash/installed
fi
;;
del) # delete only if previously installed
if grep -wq "$pkg" /tmp/.trash/installed; then
apk del "$pkg" && sed -i 's/\b'"${pkg}"'\b//' /tmp/.trash/installed
fi
;;
*)
echo "only add/del: wrong usage"; exit
;;
esac
}
_preserve() {
[ -f "$1" ] && cp "$1" "${1}.orig"
}
_restore() {
if [ -f "${1}.orig" ]; then
mv -- "${1}.orig" "${1}"
else
rm -rf "${1}"
fi
}
##### End of part to be duplicated into post-cleanup (do not alter!)
# Redirect stdout and errors to console as rc.local does not log anything
exec 1>/dev/console 2>&1
logger -st ${0##*/} "Alpine Linux headless bootstrap v$VERSION by macmpi"
install -dm 0700 /tmp/.trash
# grab used ovl filename from dmesg
ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )"
ovl="$( basename "${ovl}" )"
# search path again as mountpoint may have been changed later in the boot process...
ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 )
# Help randomness for wpa_supplicant and ssh server
rc-service seedrng start
## Setup Network interfaces
if [ -f "${ovlpath}/wpa_supplicant.conf" ]; then
logger -st ${0##*/} "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
else
logger -st ${0##*/} "No wifi setup supplied !"
fi
_preserve "/etc/network/interfaces"
if ! install -m644 "${ovlpath}/interfaces" /etc/network/interfaces; then
# set default interfaces if not specified by interface file on boot storage
logger -st ${0##*/} "No interfaces file supplied, building defaults..."
for dev in $(ls /sys/class/net)
do
case ${dev%%[0-9]*} in
lo)
cat <<-EOF >> /etc/network/interfaces
auto $dev
iface $dev inet loopback
EOF
;;
eth)
cat <<-EOF >> /etc/network/interfaces
auto $dev
iface $dev inet dhcp
EOF
;;
wlan)
[ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && cat <<-EOF >> /etc/network/interfaces
auto $dev
iface $dev inet dhcp
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
cat <<-EOF > /etc/resolv.conf
nameserver 208.67.222.222
nameserver 208.67.220.220
EOF
;;
esac
done
fi
echo "Using following network interfaces:"
cat /etc/network/interfaces
_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 start
rc-service networking start
## Setup temporary SSH server (root login, no password)
## We use some bundled (or optionaly provided) keys to avoid generation at startup and save time
_apk add openssh
_preserve "/etc/ssh/sshd_config"
_preserve "/etc/conf.d/sshd"
cat <<-EOF > /etc/ssh/sshd_config
PermitRootLogin yes
Banner /tmp/.trash/banner
EOF
if install -m600 "${ovlpath}/authorized_keys" /tmp/.trash/authorized_keys; then
logger -st ${0##*/} "Enabling public key SSH authentication..."
cat <<-EOF >> /etc/ssh/sshd_config
AuthenticationMethods publickey
AuthorizedKeysFile /tmp/.trash/authorized_keys
# relax strict mode as authorized_keys are inside /tmp
StrictModes no
EOF
else
logger -st ${0##*/} "No SSH authentication."
cat <<-EOF >> /etc/ssh/sshd_config
AuthenticationMethods none
PermitEmptyPasswords yes
EOF
fi
# Banner file
warn=""
grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; RO=$?
[ "$RO" -eq "0" ] && warn="(remount partition rw!)"
cat <<-EOF > /tmp/.trash/banner
Alpine Linux headless bootstrap v$VERSION by macmpi
You may want to delete/rename .apkovl file before reboot ${warn}:
${ovlpath}/${ovl}
(can be done automatically with unattended script - see sample snippet)
EOF
# Bundled temporary keys are moved in RAM /tmp so they won't be stored
# within permanent config later (new ones will then be generated at reboot)
KEYGEN_STANCE="sshd_disable_keygen=yes"
mv /etc/ssh/ssh_host_*_key* /tmp/.trash/.
# Inject optional custom keys (those might be stored)
if install -m600 "${ovlpath}"/ssh_host_*_key* /etc/ssh/; then
# 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
rm /etc/ssh/ssh_host_*_key*
KEYGEN_STANCE=""
logger -st ${0##*/} "Will generate new SSH keys..."
else
chmod 644 /etc/ssh/ssh_host_*_key.pub
logger -st ${0##*/} "Using injected SSH keys..."
fi
else
logger -st ${0##*/} "Using bundled ssh keys from RAM..."
cat <<-EOF >> /etc/ssh/sshd_config
HostKey /tmp/.trash/ssh_host_ed25519_key
HostKey /tmp/.trash/ssh_host_rsa_key
EOF
fi
echo "$KEYGEN_STANCE" >> /etc/conf.d/sshd
rc-service sshd start
## Prep for final post-cleanup
## clears any installed packages and settings
# copy begininng of this file to keep functions
sed -n '/^#* End .*alter!)$/q;p' /etc/local.d/headless.start > /tmp/.trash/post-cleanup
cat <<-EOF >> /tmp/.trash/post-cleanup
_tst_inet() {
## Tested 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
## Privacy policy: https://is.gd/privacy.php
INET="failed"
wget -q -T 10 --spider https://is.gd/apkovl_run > /dev/null 2>&1 &&
INET="success"
logger -st ${0##*/} "Internet access: \$INET"
}
logger -st ${0##*/} "Cleaning-up..."
_restore "/etc/ssh/sshd_config"
_restore "/etc/conf.d/sshd"
_apk del openssh
_restore "/etc/wpa_supplicant/wpa_supplicant.conf"
_apk del wpa_supplicant
_restore "/etc/network/interfaces"
_restore "/etc/hostname"
rm /etc/modules-load.d/g_ether.conf
rm /etc/modprobe.d/g_ether.conf
rc-update del local default
rm /etc/local.d/headless.start
# Internet connectivity test
# Can be skipped by creating a 'opt-out'-named dummy file aside apkovl file
[ -f "${ovlpath}/opt-out" ] || _tst_inet &
# Run unattended script if available
if [ -f "${ovlpath}/unattended.sh" ]; then
install -m755 "${ovlpath}/unattended.sh" /tmp/unattended.sh
/tmp/unattended.sh >/dev/console 2>&1 &
logger -st ${0##*/} "/tmp/unattended.sh script launched in the background with PID \$!"
fi
logger -st ${0##*/} "Done !!"
EOF
chmod +x /tmp/.trash/post-cleanup
exec /tmp/.trash/post-cleanup

View File

@ -1,6 +0,0 @@
# Copyright 2022 - 2023, macmpi
# SPDX-License-Identifier: MIT
# added to support USB-Ethernet gadget mode at boot for Pi devices
options g_ether dev_addr=ea:64:2f:e8:19:94 host_addr=f6:67:ce:b3:c0:ea

View File

@ -0,0 +1,6 @@
# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT
# support g_cdc USB-Ethernet gadget mode at boot for Pi devices
options g_cdc dev_addr=ea:64:2f:e8:19:94 host_addr=f6:67:ce:b3:c0:ea

View File

@ -1,9 +0,0 @@
# Copyright 2022 - 2023, macmpi
# SPDX-License-Identifier: MIT
# added to support USB-Ethernet gadget mode at boot for Pi devices
# also requires dtoverlay=dwc2 is added to usercfg.txt or config.txt
dwc2
g_ether

View File

@ -0,0 +1 @@
../../init.d/headless_bootstrap

View File

@ -1 +0,0 @@
/etc/init.d/local

View File

@ -0,0 +1,306 @@
#!/bin/sh
# SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT
HDLSBSTRP_VERSION="1.0"
_apk() {
local cmd="$1"
local pkg="$2"
case $cmd in
add) # install only if not already present
if ! apk info | grep -wq "${pkg}"; then
apk add "$pkg" && printf '%s ' "${pkg}" >> /tmp/.trash/installed
fi
;;
del) # delete only if previously installed
if grep -wq "$pkg" /tmp/.trash/installed > /dev/null 2>&1; then
apk del "$pkg" && sed -i 's/\b'"${pkg}"'\b//' /tmp/.trash/installed
fi
;;
*)
echo "only add/del: wrong usage"; exit
;;
esac
}
_preserve() {
# create a back-up of element (file, folder, symlink)
[ -z "${1}" ] && return 1
[ -e "$1" ] && cp -a "$1" "${1}.orig"
}
_restore() {
# remove element (file, folder, symlink) and replace by
# previous back-up if available
[ -z "${1}" ] && return 1
rm -rf "${1}"
[ -e "${1}.orig" ] && mv -f "${1}.orig" "${1}"
}
# shellcheck disable=SC2142 # known special case
alias _logger='logger -st "${0##*/}"'
##### End of part to be duplicated into headless_cleanup (do not alter!)
_prep_cleanup() {
## Prep for final headless_cleanup
# clears any installed packages and settings
# copy begininng of this file to keep functions
sed -n '/^#* End .*alter!)$/q;p' /usr/local/bin/headless_bootstrap > /tmp/.trash/headless_cleanup
cat <<-EOF >> /tmp/.trash/headless_cleanup
# Redirect stdout and errors to console as service won't show messages
exec 1>/dev/console 2>&1
_logger "Cleaning-up..."
_restore "/etc/ssh/sshd_config"
_restore "/etc/conf.d/sshd"
_apk del openssh-server
_restore "/etc/wpa_supplicant/wpa_supplicant.conf"
_apk del wpa_supplicant
_restore "/etc/network/interfaces"
_restore "/etc/hostname"
rm -f /etc/modprobe.d/headless_gadget.conf
# remove from boot service to avoid spurious openrc recalls from unattended script
rm -f /etc/runlevels/default/headless_bootstrap
rm -f /usr/local/bin/headless_bootstrap
# Run unattended script if available
install -m755 "${ovlpath}/unattended.sh" /tmp/headless_unattended > /dev/null 2>&1 && \
_logger "Starting headless_unattended service" && \
rc-service headless_unattended start
rm -f /etc/init.d/headless_*
_logger "Clean-up done, enjoy !"
cat /tmp/.trash/banner > /dev/console
if [ -c /dev/ttyGS0 ]; then
# Enabling terminal login into ttyGS0 serial for 60 sec
# no choice than making permanent change to pre 3.19 versions of /etc/securetty
grep -q "ttyGS0" /etc/securetty || echo "ttyGS0" >> /etc/securetty
/sbin/getty -L 115200 ttyGS0 vt100 &
fi
EOF
chmod +x /tmp/.trash/headless_cleanup
}
_setup_sshd() {
## Setup temporary SSH server (root login, no password)
# We use some bundled (or optionaly provided) keys to avoid generation at startup and save time
_apk add openssh-server
_preserve "/etc/ssh/sshd_config"
_preserve "/etc/conf.d/sshd"
cat <<-EOF > /etc/ssh/sshd_config
PermitRootLogin yes
Banner /tmp/.trash/banner
EOF
# Client authorized_keys or no authentication
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
AuthorizedKeysFile /tmp/.trash/authorized_keys
# relax strict mode as authorized_keys are inside /tmp
StrictModes no
EOF
else
_logger "No SSH authentication."
cat <<-EOF >> /etc/ssh/sshd_config
AuthenticationMethods none
PermitEmptyPasswords yes
EOF
fi
# Server keys: inject optional custom keys, or generate new (might be stored),
# or use bundeled ones (not stored)
local keygen_stance="sshd_disable_keygen=yes"
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
if find /etc/ssh/ -maxdepth 1 -type f -name 'ssh_host_*_key*' -empty | grep -q .; then
rm /etc/ssh/ssh_host_*_key*
keygen_stance=""
_logger "Will generate new SSH keys..."
else
chmod 644 /etc/ssh/ssh_host_*_key.pub
_logger "Using injected SSH keys..."
fi
else
_logger "Using bundled ssh keys from RAM..."
cat <<-EOF >> /etc/ssh/sshd_config
HostKey /tmp/.trash/ssh_host_ed25519_key
HostKey /tmp/.trash/ssh_host_rsa_key
EOF
fi
echo "$keygen_stance" >> /etc/conf.d/sshd
rc-service sshd restart
}
_tst_version() {
# Tested 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
# Privacy policy: https://is.gd/privacy.php
local new_vers=""
local status="failed"
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
status="success"
ver="$( grep -o "$ref.*\"" /tmp/homepg | grep -Eo '[0-9]+[\.[0-9]+]*' )"
rm -f /tmp/homepg
[ -n "$ver" ] && ! [ "$ver" = "$HDLSBSTRP_VERSION" ] && \
new_vers="!! Version $ver is available on Github project page !!" && \
_logger "$new_vers" && \
printf '%s\n\n' "$new_vers" >> /tmp/.trash/banner
fi
_logger "Internet access: $status"
}
_setup_networking() {
## Setup Network interfaces
if [ -d "/sys/class/net/wlan0" ] && [ -f "${ovlpath}/wpa_supplicant.conf" ]; 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
else
_logger "No wifi interface or setup 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
_logger "No interfaces file supplied, building defaults..."
for dev in /sys/class/net/*; do
dev="$(basename "$dev")"
case ${dev%%[0-9]*} in
lo)
cat <<-EOF >> /etc/network/interfaces
auto $dev
iface $dev inet loopback
EOF
;;
eth)
cat <<-EOF >> /etc/network/interfaces
auto $dev
iface $dev inet dhcp
EOF
;;
wlan)
[ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && cat <<-EOF >> /etc/network/interfaces
auto $dev
iface $dev inet dhcp
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
cat <<-EOF > /etc/resolv.conf
nameserver 208.67.222.222
nameserver 208.67.220.220
EOF
;;
esac
done
fi
echo "###################################"
echo "Using following network interfaces:"
cat /etc/network/interfaces
echo "###################################"
_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
}
_setup_gadget() {
# 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
# 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"
}
#############################################################################
## Main
# Redirect stdout and errors to console as service won't show messages
exec 1>/dev/console 2>&1
_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
# Determine ovl file location
# grab used ovl filename from dmesg
ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )"
if [ -f "${ovl}" ]; then
ovlpath="$( dirname "$ovl" )"
else
# search path again as mountpoint have been changed later in the boot process...
ovl="$( basename "${ovl}" )"
ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 )
ovl="${ovlpath}/${ovl}"
fi
# Create banner file
warn=""
grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; RO=$?
[ "$RO" -eq "0" ] && warn="(remount partition rw!)"
cat <<-EOF > /tmp/.trash/banner
Alpine Linux headless bootstrap v$HDLSBSTRP_VERSION by macmpi
You may want to delete/rename .apkovl file before reboot ${warn}:
${ovl}
(can be done automatically with unattended script - see sample snippet)
EOF
_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 &
# setup sshd unless unattended.sh script prevents it
grep -q "^#NO_SSH$" "${ovlpath}/unattended.sh" > /dev/null 2>&1 \
|| _setup_sshd
_prep_cleanup
_logger "Initial setup done, handing-over to clean-up"
rc-service headless_cleanup start
exit 0

View File

@ -1,4 +1,4 @@
# Copyright 2022 - 2023, macmpi # SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# Sample network interfaces file # Sample network interfaces file
@ -8,15 +8,12 @@ iface lo inet loopback
auto eth0 auto eth0
iface eth0 inet dhcp iface eth0 inet dhcp
hostname localhost
auto wlan0 auto wlan0
iface wlan0 inet dhcp iface wlan0 inet dhcp
hostname localhost
auto usb0 auto usb0
iface usb0 inet static iface usb0 inet static
address 10.42.0.2/24 address 10.42.0.2/24
gateway 10.42.0.1 gateway 10.42.0.1
hostname localhost

View File

@ -1,29 +1,45 @@
#/bin/sh #!/bin/sh
# Copyright 2022 - 2023, macmpi # SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
## collection of few code snippets as sample unnatteded actions some may find usefull ## collection of few code snippets as sample unnatteded actions some may find usefull
## will run encapusated within headless_unattended OpenRC service
## Obvious one; reminder: is run in the background # To prevent headless bootstrap script from starting sshd
echo hello world !! # only keep a single starting # on the line below
##NO_SSH
# Uncomment to enable stdout and errors redirection to console (service won't show messages)
# exec 1>/dev/console 2>&1
# shellcheck disable=SC2142 # known special case
alias _logger='logger -st "${0##*/}"'
## Obvious one; reminder: is run as background service
_logger "hello world !!"
sleep 60 sleep 60
_logger "Finished script"
######################################################## ########################################################
## This snippet removes apkovl file on volume after initial boot ## This snippet removes apkovl file on volume after initial boot
# 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/:.*$//' )"
ovl="$( basename "${ovl}" )" if [ -f "${ovl}" ]; then
# search path again as mountpoint may have been changed later in the boot process... ovlpath="$( dirname "$ovl" )"
ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 ) else
# search path again as mountpoint have been changed later in the boot process...
ovl="$( basename "${ovl}" )"
ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name "${ovl}" -exec dirname {} \; | head -1 )
ovl="${ovlpath}/${ovl}"
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; RO=$?
[ "$RO" -eq "0" ] && mount -o remount,rw "${ovlpath}" [ "$RO" -eq "0" ] && mount -o remount,rw "${ovlpath}"
rm -f "${ovlpath}/${ovl}" rm -f "${ovl}"
[ "$RO" -eq "0" ] && mount -o remount,ro "${ovlpath}" [ "$RO" -eq "0" ] && mount -o remount,ro "${ovlpath}"
######################################################## ########################################################
@ -33,7 +49,7 @@ rm -f "${ovlpath}/${ovl}"
# note: with INTERFACESOPTS=none, no networking will be setup so it won't work after reboot! # note: with INTERFACESOPTS=none, no networking will be setup so it won't work after reboot!
# Change it or run setup-interfaces in interractive mode afterwards (and lbu commit -d thenafter) # Change it or run setup-interfaces in interractive mode afterwards (and lbu commit -d thenafter)
logger -st ${0##*/} "Setting-up minimal environment" _logger "Setting-up minimal environment"
cat <<-EOF > /tmp/ANSWERFILE cat <<-EOF > /tmp/ANSWERFILE
# base answer file for setup-alpine script # base answer file for setup-alpine script
@ -88,5 +104,5 @@ lbu commit -d
######################################################## ########################################################
logger -st ${0##*/} "Finished unattended script" _logger "Finished unattended script"

View File

@ -1,4 +1,4 @@
# Copyright 2022 - 2023, macmpi # SPDX-FileCopyrightText: Copyright 2022-2023, macmpi
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# Sample wpa_supplicant.conf # Sample wpa_supplicant.conf