196 lines
6.3 KiB
Bash
Executable File
196 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# === CONFIG ===
|
|
STYX_USER="styx"
|
|
STYX_PASS="TempPass#123" # change at first login
|
|
STYX_HOSTNAME="styx"
|
|
SSH_PUBKEY="" # e.g., 'ssh-ed25519 AAAA... your@host' (optional)
|
|
|
|
# === helpers ===
|
|
require_root() {
|
|
if [[ $EUID -ne 0 ]]; then exec sudo -E "$0" "$@"; fi
|
|
}
|
|
|
|
ensure_binfmt_arm64() {
|
|
# If binfmt for arm64 isn't registered, register it via Docker (idempotent).
|
|
if [[ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]]; then
|
|
command -v docker >/dev/null || { echo "Docker required to register binfmt (sudo pacman -S docker)"; exit 1; }
|
|
sudo systemctl enable --now docker >/dev/null 2>&1 || true
|
|
sudo docker run --rm --privileged tonistiigi/binfmt --install arm64
|
|
fi
|
|
}
|
|
|
|
find_parts() {
|
|
BOOT=$(lsblk -o LABEL,PATH -nr | awk '$1=="system-boot"{print $2}' | head -n1)
|
|
ROOT=$(lsblk -o LABEL,PATH -nr | awk '$1=="writable"{print $2}' | head -n1)
|
|
if [[ -z "${BOOT:-}" || -z "${ROOT:-}" ]]; then
|
|
echo "Could not find 'system-boot'/'writable' on any device."
|
|
lsblk -o NAME,SIZE,FSTYPE,LABEL,PATH -nr
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
mount_parts() {
|
|
mkdir -p /mnt/pi-boot /mnt/pi-root
|
|
mount "$ROOT" /mnt/pi-root
|
|
mount "$BOOT" /mnt/pi-boot
|
|
|
|
# Bind only what we need (avoid /run to prevent postinst fights)
|
|
for d in dev dev/pts proc sys; do mount --bind "/$d" "/mnt/pi-root/$d"; done
|
|
|
|
# Ubuntu images use a resolv.conf symlink—replace with a real file
|
|
if [[ -L /mnt/pi-root/etc/resolv.conf || ! -e /mnt/pi-root/etc/resolv.conf ]]; then
|
|
rm -f /mnt/pi-root/etc/resolv.conf
|
|
cat /etc/resolv.conf > /mnt/pi-root/etc/resolv.conf
|
|
fi
|
|
}
|
|
|
|
prep_chroot() {
|
|
# Block service starts inside chroot (no systemd there)
|
|
cat >/mnt/pi-root/usr/sbin/policy-rc.d <<'EOF'
|
|
#!/bin/sh
|
|
exit 101
|
|
EOF
|
|
chmod +x /mnt/pi-root/usr/sbin/policy-rc.d
|
|
|
|
# All the work happens inside the ARM64 rootfs
|
|
CHCMD=$(cat <<'EOS'
|
|
set -euo pipefail
|
|
export DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC
|
|
# Ensure sbin is in PATH so user/group tools work
|
|
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
|
|
apt-get update
|
|
apt-get -y full-upgrade
|
|
|
|
# Remove snaps and keep them gone (Ubuntu for Pi ships with snaps)
|
|
apt-get -y purge snapd || true
|
|
rm -rf /snap /var/snap /var/lib/snapd /home/*/snap || true
|
|
mkdir -p /etc/apt/preferences.d
|
|
printf 'Package: snapd\nPin: release *\nPin-Priority: -10\n' > /etc/apt/preferences.d/nosnap.pref
|
|
|
|
# Ensure user/group tools exist
|
|
apt-get install -y passwd adduser || true
|
|
getent group i2c >/dev/null || /usr/sbin/groupadd i2c
|
|
|
|
# Base packages
|
|
BASE_PKGS="openssh-server git i2c-tools python3-smbus python3-pil zbar-tools qrencode lm-sensors"
|
|
apt-get install -y $BASE_PKGS
|
|
|
|
# ------- OLED (Luma) -------
|
|
# Prefer distro package; fall back to pip if not present in this release
|
|
if ! dpkg -s python3-luma.oled >/dev/null 2>&1; then
|
|
apt-get update
|
|
if ! apt-get install -y python3-luma.oled; then
|
|
apt-get install -y python3-pip
|
|
pip3 install --no-input --break-system-packages luma.oled
|
|
fi
|
|
fi
|
|
|
|
# ------- Camera apps -------
|
|
# Ubuntu renamed libcamera-apps -> rpicam-apps for Raspberry Pi.
|
|
# Try in order; tolerate absence (the box might be display-only).
|
|
apt-get update
|
|
if ! apt-get install -y rpicam-apps; then
|
|
apt-get install -y libcamera-apps || apt-get install -y libcamera-tools || true
|
|
fi
|
|
|
|
# Enable SSH on boot (no systemctl in chroot)
|
|
mkdir -p /etc/systemd/system/multi-user.target.wants
|
|
ln -sf /lib/systemd/system/ssh.service /etc/systemd/system/multi-user.target.wants/ssh.service
|
|
|
|
# Create user and set password
|
|
if ! id -u STYX_USER >/dev/null 2>&1; then
|
|
/usr/sbin/useradd -m -s /bin/bash -G sudo,video,i2c STYX_USER
|
|
fi
|
|
echo 'STYX_USER:STYX_PASS' | /usr/sbin/chpasswd
|
|
|
|
# Optional: preload SSH key
|
|
if [ -n 'SSH_PUBKEY' ] && echo 'SSH_PUBKEY' | grep -q 'ssh-'; then
|
|
install -d -m700 /home/STYX_USER/.ssh
|
|
echo 'SSH_PUBKEY' >> /home/STYX_USER/.ssh/authorized_keys
|
|
chmod 600 /home/STYX_USER/.ssh/authorized_keys
|
|
chown -R STYX_USER:STYX_USER /home/STYX_USER/.ssh
|
|
fi
|
|
|
|
# Freenove code
|
|
git clone https://github.com/Freenove/Freenove_Computer_Case_Kit_for_Raspberry_Pi.git /opt/freenove || true
|
|
|
|
# Hostname
|
|
echo 'STYX_HOSTNAME' > /etc/hostname
|
|
if grep -q '^127\.0\.1\.1' /etc/hosts; then
|
|
sed -i 's/^127\.0\.1\.1.*/127.0.1.1\tSTYX_HOSTNAME/' /etc/hosts
|
|
else
|
|
echo -e '127.0.1.1\tSTYX_HOSTNAME' >> /etc/hosts
|
|
fi
|
|
|
|
apt-get clean
|
|
EOS
|
|
)
|
|
# Inject config values safely
|
|
CHCMD="${CHCMD//STYX_USER/${STYX_USER}}"
|
|
CHCMD="${CHCMD//STYX_PASS/${STYX_PASS}}"
|
|
CHCMD="${CHCMD//STYX_HOSTNAME/${STYX_HOSTNAME}}"
|
|
CHCMD="${CHCMD//SSH_PUBKEY/${SSH_PUBKEY}}"
|
|
|
|
chroot /mnt/pi-root /bin/bash -lc "$CHCMD"
|
|
}
|
|
|
|
install_service_host() {
|
|
# Systemd unit for the Freenove example app
|
|
mkdir -p /mnt/pi-root/etc/systemd/system/multi-user.target.wants
|
|
cat >/mnt/pi-root/etc/systemd/system/freenove-case.service <<'SERVICE'
|
|
[Unit]
|
|
Description=Freenove Case OLED/Fans/LEDs
|
|
After=multi-user.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/bin/python3 /opt/freenove/Code/application.py
|
|
Restart=on-failure
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
SERVICE
|
|
ln -sf /etc/systemd/system/freenove-case.service \
|
|
/mnt/pi-root/etc/systemd/system/multi-user.target.wants/freenove-case.service || true
|
|
}
|
|
|
|
boot_tweaks() {
|
|
# Enable I2C and set DSI panel on the BOOT partition
|
|
grep -q 'dtparam=i2c_arm=on' /mnt/pi-boot/config.txt || echo 'dtparam=i2c_arm=on' >> /mnt/pi-boot/config.txt
|
|
# Append kernel cmdline only once
|
|
if ! grep -q 'DSI-1:800x480@60D' /mnt/pi-boot/cmdline.txt 2>/dev/null; then
|
|
sed -i '1 s#$# video=DSI-1:800x480@60D video=HDMI-A-1:off video=HDMI-A-2:off#' /mnt/pi-boot/cmdline.txt || true
|
|
fi
|
|
}
|
|
|
|
cleanup() {
|
|
rm -f /mnt/pi-root/usr/sbin/policy-rc.d || true
|
|
for d in dev/pts dev proc sys; do umount -lf "/mnt/pi-root/$d" 2>/dev/null || true; done
|
|
umount -lf /mnt/pi-boot 2>/dev/null || true
|
|
umount -lf /mnt/pi-root 2>/dev/null || true
|
|
sync || true
|
|
}
|
|
|
|
main() {
|
|
require_root
|
|
ensure_binfmt_arm64
|
|
find_parts
|
|
trap 'echo "ERROR at line $LINENO" >&2; cleanup' ERR INT
|
|
mount_parts
|
|
prep_chroot
|
|
install_service_host
|
|
boot_tweaks
|
|
cleanup
|
|
echo "✅ Done. Move the NVMe to the Pi and boot."
|
|
echo " Login: user '${STYX_USER}' pass '${STYX_PASS}' (change with 'passwd')."
|
|
echo " Quick checks on the Pi:"
|
|
echo " sudo i2cdetect -y 1"
|
|
echo " rpicam-still -n -o test.jpg # (if rpicam-apps installed)"
|
|
echo " libcamera-still -n -o test.jpg # (if legacy libcamera-apps installed)"
|
|
echo " systemctl status freenove-case"
|
|
}
|
|
main "$@"
|