diff --git a/LICENSE.spdx b/LICENSE.spdx new file mode 100644 index 0000000..37c5475 --- /dev/null +++ b/LICENSE.spdx @@ -0,0 +1,7 @@ +SPDXVersion: SPDX-2.1 +DataLicense: CC0-1.0 +PackageName: alpine-linux-headless-bootstrap +PackageOriginator: macmpi +PackageHomePage: https://github.com/macmpi/alpine-linux-headless-bootstrap +PackageLicenseDeclared: MIT + diff --git a/README.md b/README.md index 06756d0..ff87050 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,42 @@ # Deploy 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.\ -However, there are many cases where one might want to deploy a headless system, 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 enables a basic ssh server to log-into from another Computer, in order to finalize system setup. +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. ## Install procedure: 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). -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 the system. - -With default network interface definitions (and SSID/pass file if using wifi), one may then access the system under `ssh` with: `ssh root@`\ +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.\ +With default network interface definitions (and SSID/pass file if using wifi), system can then be remotely accessed with: `ssh root@`\ (system IP address may be determined with any IP scanning tools such as `nmap`). -As with Alpine Linux initial bring-up, `root` account has no password initially (change that after setup!).\ -From there, system install can be fine-tuned as usual with `setup-alpine` for instance (check [wiki](https://wiki.alpinelinux.org/wiki/Alpine_setup_scripts#setup-alpine) for details). +As with Alpine Linux initial bring-up, `root` account has no password initially (change that during 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). -Add-on files may be added next to `headless.apkovl.tar.gz` to customise setup (sample files are provided): -- `wpa_supplicant.conf` (*mandatory for wifi usecase*): define wifi SSID & password. -- `interfaces` (*optional*): define network interfaces at will, if defaults DCHP-based are not suitable. -- `unattended.sh` (*optional*): make custom automated deployment script to further tune & extend setup (backgrounded). +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. +- `interfaces`[^2] (*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). +- `unattended.sh`[^2] (*optional*): create custom automated deployment script to further tune & extend actual setup (backgrounded). -*Note:* 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 other). -**Goody:** seamless USB bootstrapping for PiZero devices (or similar which can support USB ethernet gadget networking):\ -Just add `dtoverlay=dwc2` in `usercfg.txt` (or `config.txt`), and plug-in USB to 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` !... +**Goody:** seamless USB bootstrapping for PiZero devices (or similar supporting USB ethernet gadget networking):\ +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` 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 /tmp, so they will **not be saved/reused** once permanent configuration is set (with or without ssh server voluntarily installed in permanent setup). +[^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). + +[^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). -## How to customize further ? +## How to customize ? 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).\ Execute `./make.sh` to rebuild `headless.apkovl.tar.gz` after changes. diff --git a/headless.apkovl.tar.gz b/headless.apkovl.tar.gz index 5faa7c9..97fe05d 100644 Binary files a/headless.apkovl.tar.gz and b/headless.apkovl.tar.gz differ diff --git a/make.sh b/make.sh index 53963d4..db2052c 100755 --- a/make.sh +++ b/make.sh @@ -1,5 +1,8 @@ #!/bin/sh +# Copyright 2022 - 2023, macmpi +# SPDX-License-Identifier: MIT + chmod 600 overlay/etc/ssh/ssh_host_*_key chmod +x overlay/etc/local.d/headless.start tar czvf headless.apkovl.tar.gz -C overlay etc --owner=0 --group=0 diff --git a/overlay/etc/local.d/headless.start b/overlay/etc/local.d/headless.start index 49b3fe8..8eec0c6 100755 --- a/overlay/etc/local.d/headless.start +++ b/overlay/etc/local.d/headless.start @@ -1,6 +1,9 @@ #!/bin/sh -VERSION="0.6" +# Copyright 2022 - 2023, macmpi +# SPDX-License-Identifier: MIT + +VERSION="0.7" # Redirect stdout and errors to console as rc.local does not log anything exec 1>/dev/console 2>&1 @@ -8,19 +11,21 @@ exec 1>/dev/console 2>&1 logger -st ${0##*/} "Alpine Linux headless bootstrap v$VERSION by macmpi" mkdir /tmp/.trash -ovlpath=$( find /media -type d -path '*/.*' -prune -o -type f -name *.apkovl.tar.gz -exec dirname {} \; | head -1 ) +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 +rc-service seedrng start ## Setup Network interfaces if [ -f "${ovlpath}/wpa_supplicant.conf" ]; then logger -st ${0##*/} "Wifi setup found !" apk add wpa_supplicant - cp "${ovlpath}/wpa_supplicant.conf" /etc/wpa_supplicant/wpa_supplicant.conf + install -m600 "${ovlpath}/wpa_supplicant.conf" /etc/wpa_supplicant/wpa_supplicant.conf else logger -st ${0##*/} "Wifi setup not found !" fi -if ! cp "${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 logger -st ${0##*/} "No interfaces file supplied, building default interfaces..." for dev in $(ls /sys/class/net) @@ -77,27 +82,50 @@ rc-service networking start ## Setup temporary SSH server (root login, no password) -## we use some bundled keys to avoid generation at boot and save time -## bundled temporary keys are moved in /tmp so they won't be stored -## within permanent config later (new ones will then be generated) +## we use some bundled or optionaly provided keys to avoid generation at startup and save time apk add openssh - -mv /etc/ssh/ssh_host_* /tmp/.trash/. - cp /etc/ssh/sshd_config /etc/ssh/sshd_config.orig +cp /etc/conf.d/sshd /etc/conf.d/sshd.orig + cat <<-EOF >> /etc/ssh/sshd_config AuthenticationMethods none PermitEmptyPasswords yes PermitRootLogin yes - HostKey /tmp/.trash/ssh_host_ed25519_key - HostKey /tmp/.trash/ssh_host_rsa_key + Banner /tmp/.trash/banner EOF -cp /etc/conf.d/sshd /etc/conf.d/sshd.orig -cat <<-EOF >> /etc/conf.d/sshd - sshd_disable_keygen=yes +# Banner file +cat <<-EOF > /tmp/.trash/banner + + Alpine Linux headless bootstrap v$VERSION by macmpi + 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) +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: generate new keys if found + 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 diff --git a/overlay/etc/modprobe.d/g_ether.conf b/overlay/etc/modprobe.d/g_ether.conf index b161bba..a3fa924 100644 --- a/overlay/etc/modprobe.d/g_ether.conf +++ b/overlay/etc/modprobe.d/g_ether.conf @@ -1,3 +1,6 @@ +# 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 diff --git a/overlay/etc/modules-load.d/g_ether.conf b/overlay/etc/modules-load.d/g_ether.conf index e5456e0..a9b7ea2 100644 --- a/overlay/etc/modules-load.d/g_ether.conf +++ b/overlay/etc/modules-load.d/g_ether.conf @@ -1,3 +1,6 @@ +# 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 diff --git a/sample_interfaces b/sample_interfaces index eefd4ef..e3f9acc 100644 --- a/sample_interfaces +++ b/sample_interfaces @@ -1,3 +1,6 @@ +# Copyright 2022 - 2023, macmpi +# SPDX-License-Identifier: MIT + # Sample network interfaces file auto lo diff --git a/sample_unattended.sh b/sample_unattended.sh index 2d8844e..58fab1b 100644 --- a/sample_unattended.sh +++ b/sample_unattended.sh @@ -1,6 +1,88 @@ #/bin/sh +# Copyright 2022 - 2023, macmpi +# SPDX-License-Identifier: MIT + +## collection of few code snippets as sample unnatteded actions some may find usefull + + +## Obvious one; reminder: is run in the background echo hello world !! sleep 60 + +######################################################## + + +## 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 ) + +# also works in case volume is mounted read-only +grep -q "${ovlpath}.*[[:space:]]ro[[:space:],]" /proc/mounts; RO=$? +[ "$RO" -eq "0" ] && mount -o remount,rw "${ovlpath}" +rm "${ovlpath}"/*.apkovl.tar.gz +[ "$RO" -eq "0" ] && mount -o remount,ro "${ovlpath}" + +######################################################## + + +## This snippet configures Minimal diskless environment +# 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) + +logger -st ${0##*/} "Setting-up minimal environment" + +cat <<-EOF > /tmp/ANSWERFILE + # base answer file for setup-alpine script + + # Do not set keyboard layout + KEYMAPOPTS=none + + # Keep hostname + HOSTNAMEOPTS="$(hostname)" + + # Set device manager to mdev + DEVDOPTS=mdev + + # Contents of /etc/network/interfaces + INTERFACESOPTS=none + + # Set Public nameserver + DNSOPTS="-n 208.67.222.222" + + # Set timezone to UTC + TIMEZONEOPTS="UTC" + + # set http/ftp proxy + PROXYOPTS=none + + # Add first mirror (CDN) + APKREPOSOPTS="-1" + + # Do not create any user + USEROPTS=none + + # No Openssh + SSHDOPTS=none + + # Use openntpd + NTPOPTS="chrony" + + # No disk install (diskless) + DISKOPTS=none + + # Setup storage for diskless (find boot directory in /media/xxxx/apk/.boot_repository) + LBUOPTS="$( find /media -maxdepth 3 -type d -path '*/.*' -prune -o -type f -name '.boot_repository' -exec dirname {} \; | head -1 | xargs dirname )" + APKCACHEOPTS="\$LBUOPTS/cache" + + EOF + +# trick setup-alpine to pretend existing SSH connection +# and therefore keep (do not reset) network interfaces while running in background +SSH_CONNECTION="FAKE" setup-alpine -ef /tmp/ANSWERFILE +lbu commit -d + +######################################################## + + logger -st ${0##*/} "Finished unattended script" diff --git a/sample_wpa_supplicant.conf b/sample_wpa_supplicant.conf index 021f3e4..ba349e1 100644 --- a/sample_wpa_supplicant.conf +++ b/sample_wpa_supplicant.conf @@ -1,3 +1,6 @@ +# Copyright 2022 - 2023, macmpi +# SPDX-License-Identifier: MIT + # Sample wpa_supplicant.conf country=FR