Skip to content
Draft
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
51b3c62
WIP
bgshacklett Jun 1, 2025
40eddef
Move unattended.sh script into separate file
bgshacklett Jun 4, 2025
7e3fcb6
WIP
bgshacklett Jun 5, 2025
e5e92ab
download headless-apkovl using raw url
bgshacklett Jun 5, 2025
716e3a6
Move cmdline.txt and usercfg.txt setup to prep script
bgshacklett Jun 5, 2025
169b518
Move lbu commit
bgshacklett Jun 5, 2025
f64073a
Don't set up /dev/sda for now
bgshacklett Jun 5, 2025
d8dcb9e
remove superfluous `main` function
bgshacklett Jun 21, 2025
1efb350
Move K8s manifests to separate directory
bgshacklett Sep 1, 2025
339b77d
Add script for running alpine in qemu on raspi4b 'machine'
bgshacklett Sep 1, 2025
fb5997d
Set up untattended config files
bgshacklett Sep 1, 2025
b2b5f28
Set up makefile skeleton
bgshacklett Sep 1, 2025
5e6654e
Update .gitignore
bgshacklett Sep 1, 2025
4587d39
Update script name
bgshacklett Sep 2, 2025
f08b540
WIP
bgshacklett Sep 7, 2025
24bf297
Output unattended.sh script logging to console
bgshacklett Sep 7, 2025
0d8b5ef
Handle WPA_SUPPLICANT
bgshacklett Sep 7, 2025
00a9022
Rename main script
bgshacklett Sep 7, 2025
4e801c0
WIP
bgshacklett Sep 14, 2025
7e8eaf1
WIP
bgshacklett Sep 14, 2025
6940b84
WIP
bgshacklett Sep 15, 2025
83e5f17
WIP
bgshacklett Sep 15, 2025
e56b309
WIP
bgshacklett Sep 21, 2025
a9c0fc2
Initial working WIFI simulation
bgshacklett Sep 27, 2025
bb5568e
Finish what alpine-setup could not
bgshacklett Sep 27, 2025
0a9d401
Default to pi3 hardware until pi4 is more usable
bgshacklett Sep 27, 2025
a4e6b01
Re-Arrange variables
bgshacklett Sep 27, 2025
ee17c69
Specify $boot path outside of _populate_config
bgshacklett Sep 27, 2025
00ddd11
cleanup
bgshacklett Sep 27, 2025
6f0f7c5
clean up comments
bgshacklett Sep 27, 2025
8649df4
Prep for writing to SD Card
bgshacklett Sep 28, 2025
2c561b0
Fix: don't mangle backslashes in WIFI settings
bgshacklett Sep 28, 2025
257c854
Set up answer file; use usb0 for networking after install
bgshacklett Sep 28, 2025
d59a14f
Enable admin user
bgshacklett Sep 29, 2025
67efcd6
verbose output for downloads
bgshacklett Sep 29, 2025
3e74b3e
Work on populating SD card
bgshacklett Oct 4, 2025
a0e4640
make unmount and removal of loop devide more robust
bgshacklett Oct 5, 2025
8fa0aac
Don't run with_p1_sd as subshell
bgshacklett Oct 5, 2025
17b303b
cleanup
bgshacklett Oct 5, 2025
f610483
Improve make sdcard logic
bgshacklett Oct 5, 2025
770e5d0
add hostname to alpine-setup.any.conf.example
bgshacklett Oct 12, 2025
9ad5924
Remove answers.txt from repo; create example of same
bgshacklett Oct 12, 2025
d3aa975
Separate default answers files for RPI and QEMU
bgshacklett Oct 12, 2025
80cbb52
Found `-e` option. Rely on setup-alpine for most of setup
bgshacklett Oct 12, 2025
7330f9f
Update device requests
bgshacklett Oct 13, 2025
86cac53
Merge branch 'main' into alpine
bgshacklett May 15, 2026
bd14297
fix: address 12 review issues across shell scripts and config files
May 16, 2026
c297322
Add unattended.lib.sh; gate WiFi prompts on tty
bgshacklett May 17, 2026
a48f76d
Pin Alpine release + verify tarball checksum
bgshacklett May 17, 2026
12d5413
Add init-config to scaffold etc/ from extras/ templates
bgshacklett May 17, 2026
cdcf2a3
Add shellcheck CI + fix existing findings; drop dead sdcard stub
bgshacklett May 17, 2026
815b9f5
Fix four cubic-flagged runtime correctness issues
bgshacklett May 17, 2026
ce2b384
Address cubic-flagged security issues
bgshacklett May 17, 2026
a9aa2ee
Rewrite README around the Alpine image-build pipeline
bgshacklett May 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.devrootfs/*
dist/*
build/*
**/*.orig
cache/*

etc/*
!etc/pre-network.d
!etc/unattended.sh
!etc/unattended.exec.d
!**/README.md
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

46 changes: 46 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# This Makefile is a wrapper around scripts in the <repo_root>/scripts
# directory, meant to enable tab completion and generally make life a little
# easier. You do not need `make` to use the contents of this repo.


.PHONY: image
image:
@sudo -E scripts/alpine-rpi.sh make-image

.PHONY: populate-boot-qemu
populate-boot-qemu:
@sudo -E scripts/alpine-rpi.sh populate-boot-qemu

.PHONY: populate-config-qemu
populate-config-qemu:
@sudo -E scripts/alpine-rpi.sh populate-config-qemu

.PHONY: verify
verify:
@scripts/alpine-rpi.sh verify

.PHONY: qemu
qemu: image populate-boot-qemu populate-config-qemu

.PHONY: launch
launch:
@sudo -E scripts/alpine-rpi.sh launch

.PHONY: test
test:
@sudo -E scripts/alpine-rpi.sh test

.PHONY: populate-boot-sd
populate-boot-sd:
@sudo -E scripts/alpine-rpi.sh populate-boot-sd

.PHONY: populate-config-sd
populate-config-sd:
@sudo -E scripts/alpine-rpi.sh populate-config-sd

.PHONY: sdcard
sdcard: populate-boot-sd populate-config-sd

.PHONY: clean
clean:
@scripts/alpine-rpi.sh clean
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Be sure to replace the placeholder variables with the correct values.

## Apply Static Manifests

k apply -f .
k apply -f ./manifests


### Helm Install
Expand Down
Empty file added etc/authorized_keys
Empty file.
122 changes: 122 additions & 0 deletions etc/pre-network.d/10-setup-wifi.qemu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/bin/sh
# shellcheck shell=dash

# shellcheck source=etc/unattended.lib.sh
. "$OVLPATH/unattended.lib.sh"


init_pre_setup_networking() {
set -x
dmesg | grep -E -i 'usb|cdc|rndis|eth'
lsmod | grep -E 'usbnet|cdc_ether|cdc_subset|rndis_host' || true
modprobe usbnet cdc_ether cdc_subset rndis_host # harmless if already present


# Set up initial network connectivity to retrieve required packages
ip link show
ip link set usb0 up

# continues only if a lease was obtained
udhcpc -i usb0 -n -q -t 5 -T 3

set +x
}


setup_prereqs() {
set -x
ntpd -n -q -p pool.ntp.org
date -u # sanity-check it's roughly correct now

# temporarily point to HTTP so we can install CA certs
echo 'http://dl-cdn.alpinelinux.org/alpine/latest-stable/main' >/etc/apk/repositories
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

apk update
apk add ca-certificates-bundle

# ensure BusyBox sees the right file (usually created by the pkg already)
ls -l /etc/ssl/cert.pem /etc/ssl/certs/ca-certificates.crt
# if /etc/ssl/cert.pem is missing for some reason:
ln -sf /etc/ssl/certs/ca-certificates.crt /etc/ssl/cert.pem

set +x
}


wait_for_wpa() { # iface timeout
iface="$1"
timeout="${2:-30}" # default: 30s
t=0
while [ "$t" -lt "$timeout" ]; do
state=$(wpa_cli -i "$iface" status 2>/dev/null | awk -F= '/^wpa_state=/{print $2}')
if [ "$state" = "COMPLETED" ]; then
echo "wpa_supplicant: authenticated on $iface"
return 0
fi
sleep 1
t=$((t + 1))
done
echo "wpa_supplicant: timeout waiting for authentication on $iface" >&2
return 1
}


set -x
init_pre_setup_networking
setup_prereqs
# ################################
# Setup wifi hardware simulation #
# ################################

# Add required packages
apk add --force-overwrite --no-cache iproute2-minimal iproute2
apk add --no-cache iw hostapd wpa_supplicant busybox-extras iptables

# Use full iproute2; you've got iproute2-minimal so /sbin/ip is fine
IP=/sbin/ip

# Radios
modprobe mac80211_hwsim radios=2

# “Router/AP” namespace owns wlan0 + usb0
# Take usb0 down and reconfigure it in an isolated network namespace
$IP link set usb0 down


# Make sure radios exist
ls /sys/class/net | grep -E '^wlan[0-9]+$' || {
echo "No wlan* found. Did modprobe mac80211_hwsim radios=2 succeed?"; exit 1; }

# pick radios (as you already do)
WLAN_LIST="$($IP -o link show | awk -F': ' '{print $2}' | grep -E '^wlan[0-9]+$' | sort)"
AP_WLAN="$(echo "$WLAN_LIST" | sed -n '1p')" # e.g., wlan0
STA_WLAN="$(echo "$WLAN_LIST" | sed -n '2p')" # e.g., wlan1

[ -n "$AP_WLAN" ] && [ -n "$STA_WLAN" ] || {
echo "Need two wlan* from hwsim; found: $WLAN_LIST"; exit 1; }

# create namespace, move uplink with ip
$IP netns add ap 2>/dev/null || true
UPLINK="$($IP -o link show | awk -F': ' '{print $2}' \
| awk '/^usb0$/ {print; exit} /^eth[0-9]+$/ && !seen[$0]++ {print; exit}')"
[ -n "$UPLINK" ] || { echo "No uplink (usb0/ethX) found"; exit 1; }

$IP link set "$UPLINK" down
$IP link set "$UPLINK" netns ap

ip link set "$AP_WLAN" down
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
AP_PHY="$(basename "$(readlink -f "/sys/class/net/$AP_WLAN/phy80211")")"
iw phy "$AP_PHY" set netns name ap


/sbin/ip netns exec ap /bin/sh -xc ". $OVLPATH/unattended.lib.sh; config_sim_ap $IP $OVLPATH"
$IP netns exec ap $IP -br link | sed 's/^/ ap : /'


$IP link set "$STA_WLAN" up
install -m600 "$OVLPATH/wpa_supplicant.conf" /etc/wpa_supplicant/wpa_supplicant.conf
# rc-service wpa_supplicant restart

_logger "hwsim up: STA (root ns, wlan1) ↔ AP (ap ns, wlan0) with NAT via usb0."

set +x
5 changes: 5 additions & 0 deletions etc/unattended.conf.d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Configuration Files for Headless Setup

Place configuration files here. They will be ignored by Git.

foo
121 changes: 121 additions & 0 deletions etc/unattended.exec.d/70-setup-diskless.any.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/bin/sh

# shellcheck disable=SC3040 # See: https://blog.toast.cafe/posix2024-xcu
set -euo pipefail

# shellcheck source=etc/unattended.lib.sh
. "$BOOT/unattended.lib.sh"


# ============================================================
# Configuration (override with env vars before running)
# ============================================================
: "${SSH_PORT:=22}" # change if you want a non-standard port
: "${DISABLE_ROOT:=yes}" # yes|no (locks root & forbids root SSH)
: "${RANDOMIZE_ROOT_PW:=no}" # yes|no (ignored if DISABLE_ROOT=yes)
: "${EXTRA_PACKAGES:=}" # space-separated list, optional


_die() { >&2 echo "FATAL: $1"; exit 1; }

need() {
# Install packages if missing; works in install mode too.
# Avoids --no-cache so we can benefit from local/persistent cache if
# configured.
>&2 echo "Ensuring package installation for: $*"
apk add -v --update "$@" || _die "Could not install required package."
}

file_has() { [ -f "$1" ] && grep -q "$2" "$1"; }

harden_sshd() {
need openssh
# Ensure service enabled
rc-update add sshd default >/dev/null 2>&1 || true

# Basic hardening and custom port (idempotent edits)
ss="/etc/ssh/sshd_config"
[ -f "$ss" ] || touch "$ss"

# Replace or add settings:
set_kv() {
key="$1"; val="$2"
if grep -qi "^\\s*${key}\\b" "$ss"; then
sed -i "s|^[#[:space:]]*${key}.*|${key} ${val}|I" "$ss"
else
printf '%s %s\n' "$key" "$val" >> "$ss"
fi
}

set_kv Port "$SSH_PORT"
set_kv PasswordAuthentication no
set_kv ChallengeResponseAuthentication no
set_kv KbdInteractiveAuthentication no
set_kv PermitRootLogin prohibit-password

if [ "$DISABLE_ROOT" = "yes" ]; then
set_kv PermitRootLogin no
passwd -l root >/dev/null 2>&1 || true
elif [ "$RANDOMIZE_ROOT_PW" = "yes" ]; then
# Random 24 bytes, base64—strip slashes to avoid any surprises
pw="$(dd if=/dev/urandom bs=24 count=1 2>/dev/null | base64 | tr -d '/=[:space:]' | cut -c1-32)"
echo "root:$pw" | chpasswd
_logger "Random root password set."
fi

# Start if possible (may be harmless in install mode if not running yet)
rc-service sshd restart >/dev/null 2>&1 || true
}



setup_alpine() {
need envsubst

SETUP_OPTS="-e" # Setting root password fails in unattended setup.

LBUOPTS="$(find /media \
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
-maxdepth 3 \
-type d \
-path '*/.*' \
-prune -o \
-type f \
-name '.boot_repository' \
-exec dirname {} \; \
| head -1 \
| xargs dirname)"

export LBUOPTS

envsubst < "${BOOT}/answers.txt" > /tmp/ANSWERFILE

SSH_CONNECTION="FAKE" setup-alpine "$SETUP_OPTS" -f /tmp/ANSWERFILE
}


install_extras() {
[ -n "$EXTRA_PACKAGES" ] || return 0

# shellcheck disable=2086 # We rely on splitting here
need $EXTRA_PACKAGES
}

main() {
_logger "Setting-up Alpine for Persistent Use"

# Retrieve WiFi config from wpa_supplicant.conf
INTERFACESOPTS_SSID="$(grep '^\sssid=' "$BOOT/wpa_supplicant.conf" \
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
| cut -d = -f 2 \
| tr -d '"')"

INTERFACESOPTS_PSK="$(grep '^\spsk=' "$BOOT/wpa_supplicant.conf" \
| cut -d = -f 2)"

setup_alpine
harden_sshd
install_extras

_logger "Unattended finish: done."
}

main "$@"
5 changes: 5 additions & 0 deletions etc/unattended.exec.d/80-setup-i2c.any.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh

# Autoload I²C modules
mkdir -p /etc/modules-load.d
printf '%s\n' i2c_bcm2835 i2c_dev > /etc/modules-load.d/i2c.conf
11 changes: 11 additions & 0 deletions etc/unattended.exec.d/90-cleanup.any.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh

# shellcheck disable=SC3040 # See: https://blog.toast.cafe/posix2024-xcu
set -euo pipefail


shred -u /media/mmcblk0p1/wpa_supplicant.conf 2>/dev/null || true
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated

# TODO: Remove apkovl

# rm -f /media/mmcblk0p1/unattended.sh
Loading