Compare commits

...

5 Commits

Author SHA1 Message Date
macmpi a2d5c8a304
Merge pull request #12 from macmpi/dev
Release 0.8
2023-05-17 19:16:26 +02:00
macmpi d40447c6fa bump version to 0.8 2023-05-17 19:13:41 +02:00
macmpi f10e0c0def Update readme: original state preservation notes 2023-05-17 09:56:05 +02:00
macmpi 51e426e9bf preserve original config files better
this would allow to also use apkovl to rescue existing disk-based setup
2023-05-16 14:36:41 +02:00
macmpi 1eca862849 improve overlay file detection
take name from dmesg
add suggestion for removal in banner
2023-05-16 09:30:02 +02:00
4 changed files with 94 additions and 34 deletions

View File

@ -1,16 +1,16 @@
# Deploy Alpine Linux on a headless system # Bootstrap Alpine Linux on a headless system
[Alpine Linux documentation](https://docs.alpinelinux.org/user-handbook/0.1a/Installing/setup_alpine.html) assumes **initial setup** is carried-out on a system with a keyboard & display to interract with.\ [Alpine Linux documentation](https://docs.alpinelinux.org/user-handbook/0.1a/Installing/setup_alpine.html) assumes **initial setup** is carried-out on a system with a keyboard & display to interract with.\
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 boot such headless system (leveraging Alpine distro's `initramfs` feature): it starts a basic ssh server to log-into from another Computer, in order to then perform actual system setup. 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.
## Install 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://github.com/macmpi/alpine-linux-headless-bootstrap/raw/main/headless.apkovl.tar.gz)[^1] 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://github.com/macmpi/alpine-linux-headless-bootstrap/raw/main/headless.apkovl.tar.gz)[^2] overlay file 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 if using wifi), system can then be remotely accessed with: `ssh root@<IP>`\ With default network interface definitions (and SSID/pass 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`).
@ -19,21 +19,23 @@ From there, actual system install can be performed as usual with `setup-alpine`
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`[^2] (*mandatory for wifi usecase*): define wifi SSID & password. - `wpa_supplicant.conf`[^3] (*mandatory for wifi usecase*): define wifi SSID & password.
- `interfaces`[^2] (*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.
- `ssh_host_*_key*` (*optional*): provide custom ssh keys to be injected (may be stored), instead of using bundled ones[^1] (not stored). Providing an empty key file will trigger new keys generation (ssh server may take longer to start). - `ssh_host_*_key*` (*optional*): provide 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`[^2] (*optional*): create custom automated deployment script to further tune & extend actual setup (backgrounded). - `unattended.sh`[^3] (*optional*): create custom automated deployment script to further tune & extend actual setup (backgrounded).
**Goody:** seamless USB bootstrapping for PiZero devices (or similar supporting USB ethernet gadget networking):\ **Goody:** seamless USB-ethernet gadget boostrapping (PiZero for instance):\
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` in `usercfg.txt` (or `config.txt`), and plug USB cable into 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` 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`
Main execution steps are logged in `/var/log/messages`. Main execution steps are logged in `/var/log/messages`.
[^1]: About bundled ssh keys: as this package is essentially intended to **quickly bootstrap** system in order to configure it, 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, so they will **not be saved/reused** once permanent configuration is set (with or without ssh server voluntarily installed in permanent setup). [^1]: Initial boot fully preserves system's original state (config files & installed packages): a fresh system will therefore come-up as unconfigured.
[^2]: 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). [^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).
[^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).
## How to customize ? ## How to customize ?

Binary file not shown.

View File

@ -3,7 +3,44 @@
# Copyright 2022 - 2023, macmpi # Copyright 2022 - 2023, macmpi
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
VERSION="0.7" VERSION="0.8"
_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 dupplicated into post-cleanup (do not alter!)
# Redirect stdout and errors to console as rc.local does not log anything # Redirect stdout and errors to console as rc.local does not log anything
exec 1>/dev/console 2>&1 exec 1>/dev/console 2>&1
@ -11,20 +48,25 @@ exec 1>/dev/console 2>&1
logger -st ${0##*/} "Alpine Linux headless bootstrap v$VERSION by macmpi" logger -st ${0##*/} "Alpine Linux headless bootstrap v$VERSION by macmpi"
mkdir /tmp/.trash mkdir /tmp/.trash
ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name *.apkovl.tar.gz -exec dirname {} \; | head -1 )
# Help randomess for wpa_supplicant and ssh server # grab used ovl filename from dmesg
ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )"
ovlpath="$( dirname "$ovl" )"
# Help randomness for wpa_supplicant and ssh server
rc-service seedrng start rc-service seedrng start
## Setup Network interfaces ## Setup Network interfaces
if [ -f "${ovlpath}/wpa_supplicant.conf" ]; then if [ -f "${ovlpath}/wpa_supplicant.conf" ]; then
logger -st ${0##*/} "Wifi setup found !" logger -st ${0##*/} "Wifi setup found !"
apk add wpa_supplicant _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
else else
logger -st ${0##*/} "Wifi setup not found !" logger -st ${0##*/} "Wifi setup not found !"
fi fi
_preserve "/etc/network/interfaces"
if ! install -m644 "${ovlpath}/interfaces" /etc/network/interfaces; then if ! install -m644 "${ovlpath}/interfaces" /etc/network/interfaces; then
# set default interfaces if not specified by interface file on boot storage # set default interfaces if not specified by interface file on boot storage
logger -st ${0##*/} "No interfaces file supplied, building default interfaces..." logger -st ${0##*/} "No interfaces file supplied, building default interfaces..."
@ -74,18 +116,21 @@ fi
echo "Using following network interfaces:" echo "Using following network interfaces:"
cat /etc/network/interfaces cat /etc/network/interfaces
_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 start grep -q "wlan" /etc/network/interfaces && \
[ -f /etc/wpa_supplicant/wpa_supplicant.conf ] && \
rc-service wpa_supplicant start
rc-service networking start rc-service networking start
## 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 _apk add openssh
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.orig _preserve "/etc/ssh/sshd_config"
cp /etc/conf.d/sshd /etc/conf.d/sshd.orig _preserve "/etc/conf.d/sshd"
cat <<-EOF >> /etc/ssh/sshd_config cat <<-EOF >> /etc/ssh/sshd_config
AuthenticationMethods none AuthenticationMethods none
@ -95,20 +140,29 @@ cat <<-EOF >> /etc/ssh/sshd_config
EOF EOF
# Banner file # Banner file
warn=""
grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; RO=$?
[ "$RO" -eq "0" ] && warn="(remount partition rw!)"
cat <<-EOF > /tmp/.trash/banner cat <<-EOF > /tmp/.trash/banner
Alpine Linux headless bootstrap v$VERSION by macmpi Alpine Linux headless bootstrap v$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 EOF
# Bundled temporary keys are moved in RAM /tmp so they won't be stored # Bundled temporary keys are moved in RAM /tmp so they won't be stored
# within permanent config later (new ones will then be generated) # within permanent config later (new ones will then be generated at reboot)
KEYGEN_STANCE="sshd_disable_keygen=yes" KEYGEN_STANCE="sshd_disable_keygen=yes"
mv /etc/ssh/ssh_host_*_key* /tmp/.trash/. mv /etc/ssh/ssh_host_*_key* /tmp/.trash/.
# Inject optional custom keys (those might be stored) # Inject optional custom keys (those might be stored)
if install -m600 "${ovlpath}"/ssh_host_*_key* /etc/ssh/; then if install -m600 "${ovlpath}"/ssh_host_*_key* /etc/ssh/; then
# check for empty key within injected ones: generate new keys if found # 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,16 +184,19 @@ rc-service sshd start
## Prep for final post-cleanup ## Prep for final post-cleanup
## clears any installed packages and settings ## clears any installed packages and settings
cat <<-EOF > /tmp/.trash/post-cleanup # copy begininng of this file to keep functions
#!/bin/sh sed -n '/^#* End .*alter!)$/q;p' /etc/local.d/headless.start > /tmp/.trash/post-cleanup
cat <<-EOF >> /tmp/.trash/post-cleanup
logger -st ${0##*/} "Cleaning-up..." logger -st ${0##*/} "Cleaning-up..."
mv /etc/ssh/sshd_config.orig /etc/ssh/sshd_config _restore "/etc/ssh/sshd_config"
mv /etc/conf.d/sshd.orig /etc/conf.d/sshd _restore "/etc/conf.d/sshd"
apk del openssh _apk del openssh
apk del wpa_supplicant _restore "/etc/wpa_supplicant/wpa_supplicant.conf"
rm -rf /etc/wpa_supplicant _apk del wpa_supplicant
rm /etc/network/interfaces _restore "/etc/network/interfaces"
rm /etc/hostname _restore "/etc/hostname"
rm /etc/modules-load.d/g_ether.conf rm /etc/modules-load.d/g_ether.conf
rm /etc/modprobe.d/g_ether.conf rm /etc/modprobe.d/g_ether.conf
rc-update del local default rc-update del local default

View File

@ -14,12 +14,13 @@ sleep 60
## This snippet removes apkovl file on volume after initial boot ## This snippet removes apkovl file on volume after initial boot
ovlpath=$( find /media -maxdepth 2 -type d -path '*/.*' -prune -o -type f -name *.apkovl.tar.gz -exec dirname {} \; | head -1 ) ovl="$( dmesg | grep -o 'Loading user settings from .*:' | awk '{print $5}' | sed 's/:.*$//' )"
ovlpath="$( dirname "$ovl" )"
# 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 "${ovlpath}"/*.apkovl.tar.gz rm -f "${ovl}"
[ "$RO" -eq "0" ] && mount -o remount,ro "${ovlpath}" [ "$RO" -eq "0" ] && mount -o remount,ro "${ovlpath}"
######################################################## ########################################################