187 lines
7.2 KiB
Bash
187 lines
7.2 KiB
Bash
#!/bin/busybox sh
|
|
# planck netboot installer - runs from initramfs, wipes /dev/sda, installs
|
|
# Raspberry Pi OS Lite from HTTP template, then reboots. The bootloader's
|
|
# self-update then applies the per-node pieeprom-revert.upd we PUT to the
|
|
# TFTP server, reverting boot order to USB-first, and the fresh OS boots.
|
|
export PATH=/bin:/sbin:/usr/bin:/usr/sbin
|
|
/bin/busybox --install -s /bin
|
|
|
|
SERVER=192.168.1.157
|
|
HTTP=http://$SERVER:8000
|
|
|
|
mkdir -p /proc /sys /dev /tmp /mnt/root /mnt/boot /mnt/stage /chk /conf
|
|
mount -t proc proc /proc
|
|
mount -t sysfs sysfs /sys
|
|
mount -t devtmpfs devtmpfs /dev 2>/dev/null || true
|
|
|
|
log() { echo "[planck-installer] $*"; }
|
|
fail() { log "FATAL: $*"; sleep 3600; reboot -f; }
|
|
report() { wget -q -O /dev/null "$HTTP/events/$1" 2>/dev/null; }
|
|
|
|
log "planck installer starting"
|
|
|
|
# test mode (kexec): verify kernel+initramfs+network without touching disks
|
|
if grep -q planck_test /proc/cmdline 2>/dev/null; then
|
|
SERIAL=$(awk '/Serial/ {print $3}' /proc/cpuinfo)
|
|
HOST=$(awk -v s="$SERIAL" 'tolower($1)==tolower(s) {print $2}' /conf/hostmap)
|
|
log "TEST MODE on $HOST"
|
|
report "$HOST-test-start"
|
|
ifconfig eth0 up
|
|
udhcpc -i eth0 -n -q -s /etc/udhcpc.script >/dev/null 2>&1 && log "dhcp ok" || log "dhcp FAIL"
|
|
IP=$(ip -4 -o addr show eth0 | awk '{print $4}' | cut -d/ -f1)
|
|
log "ip $IP"
|
|
report "$HOST-test-dhcp-$IP"
|
|
wget -q -O /tmp/test.bin "$HTTP/planck-http-server.py" && log "http get ok" || log "http FAIL"
|
|
report "$HOST-test-http"
|
|
sleep 5
|
|
poweroff -f
|
|
fi
|
|
|
|
report "started"
|
|
|
|
# --- identify this node -------------------------------------------------
|
|
MAC=$(cat /sys/class/net/eth0/address)
|
|
SERIAL=$(awk '/Serial/ {print $3}' /proc/cpuinfo)
|
|
HOST=$(awk -v s="$SERIAL" 'tolower($1)==tolower(s) {print $2}' /conf/hostmap)
|
|
if [ -z "$HOST" ]; then
|
|
log "unknown serial $SERIAL (mac $MAC) - not in hostmap, aborting"
|
|
report "unknown-serial-$SERIAL"
|
|
sleep 3600
|
|
reboot -f
|
|
fi
|
|
log "identity: $HOST serial=$SERIAL mac=$MAC"
|
|
report "$HOST-start"
|
|
|
|
# --- network ------------------------------------------------------------
|
|
cat > /etc/udhcpc.script <<'EOF'
|
|
#!/bin/sh
|
|
[ "$1" = bound ] || exit 0
|
|
ifconfig "$interface" "$ip" netmask "$subnet" 2>/dev/null
|
|
[ -n "$router" ] && route add default gw "$router" 2>/dev/null
|
|
EOF
|
|
chmod +x /etc/udhcpc.script
|
|
ifconfig eth0 up
|
|
udhcpc -i eth0 -n -q -s /etc/udhcpc.script >/dev/null 2>&1 && log "dhcp ok" || log "dhcp failed (continuing)"
|
|
IP=$(ip -4 -o addr show eth0 | awk '{print $4}' | cut -d/ -f1)
|
|
log "ip: ${IP:-none}"
|
|
report "$HOST-ip-${IP:-none}"
|
|
|
|
# --- idempotence: if a planck OS is already installed, just boot it -----
|
|
if [ -b /dev/sda2 ]; then
|
|
if mount -t ext4 /dev/sda2 /chk 2>/dev/null; then
|
|
if [ -f /chk/etc/hostname ] && grep -q "^planck" /chk/etc/hostname 2>/dev/null; then
|
|
log "planck OS already installed on sda2 - booting it directly"
|
|
report "$HOST-reentry"
|
|
mount -t vfat /dev/sda1 /chk/boot/firmware 2>/dev/null
|
|
cd /
|
|
exec switch_root /chk /sbin/init
|
|
fi
|
|
umount /chk
|
|
fi
|
|
fi
|
|
|
|
# --- fetch template -----------------------------------------------------
|
|
log "downloading template (this takes a minute)..."
|
|
wget -q -O /tmp/rootfs.tar "$HTTP/rootfs.tar" || fail "template download"
|
|
log "template downloaded: $(du -h /tmp/rootfs.tar | cut -f1)"
|
|
report "$HOST-template-ok"
|
|
|
|
# --- partition + format -------------------------------------------------
|
|
log "partitioning /dev/sda"
|
|
dd if=/dev/zero of=/dev/sda bs=1M count=20 2>/dev/null
|
|
sfdisk /dev/sda >/dev/null 2>&1 <<'EOF'
|
|
label: dos
|
|
start=2048, size=524288, type=0c, bootable
|
|
type=83
|
|
EOF
|
|
[ $? -eq 0 ] || fail "sfdisk"
|
|
mke2fs -F -t ext4 -L ROOTFS /dev/sda2 >/dev/null 2>&1 || fail "mkfs.ext4"
|
|
mkfs.vfat -F 32 -n BOOTFS /dev/sda1 >/dev/null 2>&1 || fail "mkfs.vfat"
|
|
mount -t ext4 /dev/sda2 /mnt/root || fail "mount root"
|
|
mount -t vfat /dev/sda1 /mnt/boot || fail "mount boot"
|
|
log "filesystems ready"
|
|
|
|
# --- extract ------------------------------------------------------------
|
|
mkdir -p /mnt/stage
|
|
tar xf /tmp/rootfs.tar -C /mnt/stage || fail "untar"
|
|
cp -a /mnt/stage/root/. /mnt/root/ || fail "copy root"
|
|
cp -a /mnt/stage/boot/. /mnt/boot/ || fail "copy boot"
|
|
rm -f /tmp/rootfs.tar
|
|
rm -rf /mnt/stage
|
|
log "rootfs + boot files extracted"
|
|
report "$HOST-extracted"
|
|
|
|
# --- configure ----------------------------------------------------------
|
|
echo "$HOST" > /mnt/root/etc/hostname
|
|
grep -q "127.0.1.1" /mnt/root/etc/hosts 2>/dev/null || echo "127.0.1.1 localhost" > /mnt/root/etc/hosts
|
|
echo "127.0.1.1 $HOST" >> /mnt/root/etc/hosts
|
|
cat > /mnt/root/etc/fstab <<'EOF'
|
|
/dev/sda1 /boot/firmware vfat defaults 0 2
|
|
/dev/sda2 / ext4 defaults,noatime 0 1
|
|
EOF
|
|
|
|
# user + keys (SSH-key-only: password locked, sudo NOPASSWD)
|
|
PASSWD=$(cat /conf/nodepass)
|
|
mount -t proc proc /mnt/root/proc 2>/dev/null
|
|
chroot /mnt/root /bin/sh -c "
|
|
set -e
|
|
useradd -m -s /bin/bash -G sudo,adm,dialout,plugdev,input,netdev,render,games,users,video adamcarr
|
|
echo \"adamcarr:$PASSWD\" | chpasswd
|
|
mkdir -p /home/adamcarr/.ssh
|
|
cp /conf/authorized_keys /home/adamcarr/.ssh/authorized_keys
|
|
chown -R adamcarr:adamcarr /home/adamcarr/.ssh
|
|
chmod 700 /home/adamcarr/.ssh
|
|
chmod 600 /home/adamcarr/.ssh/authorized_keys
|
|
echo 'adamcarr ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/010-adamcarr
|
|
chmod 440 /etc/sudoers.d/010-adamcarr
|
|
" || fail "user setup"
|
|
umount /mnt/root/proc 2>/dev/null
|
|
|
|
# ssh: fresh host keys per node, service enabled
|
|
rm -f /mnt/root/etc/ssh/ssh_host_*
|
|
chroot /mnt/root /bin/sh -c "ssh-keygen -A" || fail "ssh host keys"
|
|
ln -sf /usr/lib/systemd/system/ssh.service /mnt/root/etc/systemd/system/multi-user.target.wants/ssh.service
|
|
|
|
# firstboot service: catch-up EEPROM updates + tidy markers
|
|
cat > /mnt/root/usr/local/sbin/planck-firstboot.sh <<'EOF'
|
|
#!/bin/sh
|
|
rpi-eeprom-update -a >/dev/null 2>&1 || true
|
|
rm -f /boot/firmware/pieeprom-revert.upd /boot/firmware/pieeprom-revert.sig
|
|
rm -f /etc/planck-fresh-install
|
|
systemctl disable planck-firstboot.service >/dev/null 2>&1 || true
|
|
EOF
|
|
chmod +x /mnt/root/usr/local/sbin/planck-firstboot.sh
|
|
cat > /mnt/root/etc/systemd/system/planck-firstboot.service <<'EOF'
|
|
[Unit]
|
|
Description=planck firstboot tidy-up
|
|
After=multi-user.target
|
|
ConditionPathExists=/etc/planck-fresh-install
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/sbin/planck-firstboot.sh
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
ln -sf /etc/systemd/system/planck-firstboot.service /mnt/root/etc/systemd/system/multi-user.target.wants/planck-firstboot.service
|
|
touch /mnt/root/etc/planck-fresh-install
|
|
|
|
# cmdline for the installed OS
|
|
printf 'console=serial0,115200 console=tty1 root=/dev/sda2 rootfstype=ext4 fsck.repair=yes rootwait quiet\n' > /mnt/boot/cmdline.txt
|
|
|
|
# stage eeprom revert on the new boot partition too (ts-guard prevents reflash)
|
|
cp /conf/pieeprom-revert.upd /conf/pieeprom-revert.sig /mnt/boot/ 2>/dev/null
|
|
|
|
sync
|
|
report "$HOST-installed"
|
|
|
|
# --- hand revert EEPROM to bootloader via TFTP self-update --------------
|
|
# busybox wget can only POST, so the helper server treats POST as upload.
|
|
wget -q -O /dev/null --post-file=/conf/pieeprom-revert.upd "$HTTP/tftp/$SERIAL/pieeprom.upd" 2>/dev/null || fail "revert PUT"
|
|
wget -q -O /dev/null --post-file=/conf/pieeprom-revert.sig "$HTTP/tftp/$SERIAL/pieeprom.sig" 2>/dev/null || fail "revert sig PUT"
|
|
|
|
log "install complete - rebooting"
|
|
sleep 3
|
|
reboot -f
|