143 lines
6.7 KiB
Bash
Executable File
143 lines
6.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Install Guix (this project's release) on a GL.iNet GL-MT6000 that still has
|
|
# GL's stock U-Boot -- from stock firmware, OpenWrt, or an earlier install.
|
|
#
|
|
# sudo ./install-mt6000.sh IFACE [RELEASE-DIR]
|
|
#
|
|
# IFACE: this computer's Ethernet port, cabled to one of the router's LAN
|
|
# ports (not the 2.5G WAN port). RELEASE-DIR: where the release files are
|
|
# (default: this script's directory).
|
|
#
|
|
# What happens (docs: INSTALL.md, PITFALLS.md #51):
|
|
# 1. IFACE gets 192.168.1.2/24; a TFTP server offers OpenWrt's official
|
|
# initramfs (downloaded, checksum-verified) the way GL's U-Boot asks for
|
|
# it -- about 3 s after every power-on it fetches openwrt-gl-mt6000.bin
|
|
# from 192.168.1.2. You power-cycle the router.
|
|
# 2. OpenWrt runs from RAM at 192.168.1.1; over SSH this writes the Guix
|
|
# root file system to the `rootfs' partition (p7) and the boot image to
|
|
# the `kernel' partition (p6), and checks both against the release.
|
|
# 3. The router reboots into Guix: the wired ports are then the private
|
|
# network, 192.168.2.1 (DHCP); IFACE goes back to DHCP for it.
|
|
# Everything on p6 and p7 is replaced (the old system, its settings). The
|
|
# `factory' partition (MAC addresses, WiFi calibration) and the bootloader
|
|
# are not touched.
|
|
set -euo pipefail
|
|
|
|
IFACE=${1:?usage: sudo $0 IFACE [RELEASE-DIR]}
|
|
REL=$(realpath "${2:-$(dirname "$0")}")
|
|
[ "$(id -u)" = 0 ] || { echo "run as root (sudo)" >&2; exit 1; }
|
|
|
|
OPENWRT_URL=https://downloads.openwrt.org/releases/25.12.5/targets/mediatek/filogic/openwrt-25.12.5-mediatek-filogic-glinet_gl-mt6000-initramfs-kernel.bin
|
|
OPENWRT_SHA256=d4a86e33260f6fc3e4950438647455e7675e5c6f74fc7e00025255674f8c1a39
|
|
ROOT=$REL/guix-router-mt6000-root.ext4.zst
|
|
FIT=$REL/guix-router-mt6000-boot.itb
|
|
ROUTER=192.168.1.1
|
|
HOST=192.168.1.2
|
|
|
|
need() { command -v "$1" >/dev/null || { echo "missing: $1" >&2; exit 1; }; }
|
|
for c in dnsmasq ssh curl zstd sha256sum ip nc setsid; do need "$c"; done
|
|
for f in "$ROOT" "$FIT" "$REL/SHA256SUMS"; do
|
|
[ -f "$f" ] || { echo "missing release file: $f" >&2; exit 1; }
|
|
done
|
|
ip link show "$IFACE" >/dev/null
|
|
|
|
echo "== checking the release files"
|
|
(cd "$REL" && sha256sum -c --ignore-missing SHA256SUMS) | sed 's/^/ /'
|
|
root_sha=$(sed -n 's/^\([0-9a-f]\{64\}\) .*guix-router-mt6000-root.ext4$/\1/p' "$REL/SHA256SUMS.uncompressed")
|
|
root_size=$(sed -n 's/^\([0-9]*\) guix-router-mt6000-root.ext4$/\1/p' "$REL/SIZES.uncompressed")
|
|
[ -n "$root_sha" ] && [ -n "$root_size" ] || { echo "no uncompressed root checksum/size in the release" >&2; exit 1; }
|
|
|
|
work=$(mktemp -d)
|
|
nm_was_managed=
|
|
cleanup() {
|
|
[ -f "$work/dnsmasq.pid" ] && kill "$(cat "$work/dnsmasq.pid")" 2>/dev/null || true
|
|
ip addr del $HOST/24 dev "$IFACE" 2>/dev/null || true
|
|
if [ -n "$nm_was_managed" ]; then
|
|
nmcli device set "$IFACE" managed yes 2>/dev/null || true
|
|
fi
|
|
rm -rf "$work"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
echo "== OpenWrt initramfs (to run the installation from RAM)"
|
|
cache=${XDG_CACHE_HOME:-/var/cache}/guix-router; mkdir -p "$cache"
|
|
initramfs=$cache/$(basename "$OPENWRT_URL")
|
|
if [ ! -f "$initramfs" ]; then
|
|
curl -fL -o "$initramfs.part" "$OPENWRT_URL"
|
|
mv "$initramfs.part" "$initramfs"
|
|
fi
|
|
echo "$OPENWRT_SHA256 $initramfs" | sha256sum -c --quiet || { rm -f "$initramfs"; echo "bad download" >&2; exit 1; }
|
|
|
|
# GL's U-Boot applies only sysupgrade tars with a root: OpenWrt's initramfs
|
|
# as `kernel' (written to p6 and booted) and a dummy 1 MiB `root' (p7, which
|
|
# gets the real root next anyway).
|
|
mkdir -p "$work/tar/sysupgrade-glinet_gl-mt6000" "$work/tftp"
|
|
printf 'BOARD=glinet_gl-mt6000\n' > "$work/tar/sysupgrade-glinet_gl-mt6000/CONTROL"
|
|
cp "$initramfs" "$work/tar/sysupgrade-glinet_gl-mt6000/kernel"
|
|
head -c 1048576 /dev/zero > "$work/tar/sysupgrade-glinet_gl-mt6000/root"
|
|
tar --owner=root --group=root -C "$work/tar" -cf "$work/tftp/openwrt-gl-mt6000.bin" \
|
|
sysupgrade-glinet_gl-mt6000/CONTROL sysupgrade-glinet_gl-mt6000/kernel \
|
|
sysupgrade-glinet_gl-mt6000/root
|
|
|
|
echo "== $IFACE: $HOST/24"
|
|
if command -v nmcli >/dev/null && nmcli -t -f DEVICE,STATE device | grep -q "^$IFACE:[^u]"; then
|
|
nmcli device set "$IFACE" managed no; nm_was_managed=1
|
|
fi
|
|
ip link set "$IFACE" up
|
|
ip addr add $HOST/24 dev "$IFACE" 2>/dev/null || true
|
|
|
|
dnsmasq --keep-in-foreground --log-facility="$work/tftp.log" --user=root --port=0 \
|
|
--enable-tftp --tftp-root="$work/tftp" --interface="$IFACE" --bind-interfaces \
|
|
--conf-file=/dev/null --pid-file="$work/dnsmasq.pid" &
|
|
sleep 1
|
|
echo
|
|
echo ">> Now power-cycle the router (unplug, plug back in). Waiting..."
|
|
until grep -q 'sent .*openwrt-gl-mt6000.bin' "$work/tftp.log" 2>/dev/null; do sleep 1; done
|
|
echo "== U-Boot fetched the image; OpenWrt is starting from RAM"
|
|
kill "$(cat "$work/dnsmasq.pid")"; rm -f "$work/dnsmasq.pid"
|
|
|
|
# Fresh OpenWrt: root, empty password.
|
|
printf '#!/bin/sh\necho\n' > "$work/askpass"; chmod +x "$work/askpass"
|
|
ow() {
|
|
SSH_ASKPASS="$work/askpass" SSH_ASKPASS_REQUIRE=force setsid -w ssh \
|
|
-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
-o LogLevel=ERROR -o PubkeyAuthentication=no -o ConnectTimeout=5 \
|
|
root@$ROUTER "$@"
|
|
}
|
|
for i in $(seq 1 60); do nc -z -w2 $ROUTER 22 2>/dev/null && break; sleep 2; done
|
|
sleep 3
|
|
[ "$(ow 'grep -c mmcblk /proc/mounts' </dev/null)" = 0 ] ||
|
|
{ echo "192.168.1.1 is not OpenWrt running from RAM; stopping" >&2; exit 1; }
|
|
ow 'grep -q glinet,gl-mt6000 /tmp/sysinfo/board_name 2>/dev/null || grep -q "GL-MT6000" /proc/device-tree/model' </dev/null ||
|
|
{ echo "not a GL-MT6000; stopping" >&2; exit 1; }
|
|
|
|
echo "== writing the root file system (p7, $((root_size / 1048576)) MiB)"
|
|
zstd -dc "$ROOT" | ow 'dd of=/dev/mmcblk0p7 bs=1M 2>/dev/null; sync'
|
|
got=$(ow "echo 3 > /proc/sys/vm/drop_caches; head -c $root_size /dev/mmcblk0p7 | sha256sum" </dev/null | cut -c1-64)
|
|
[ "$got" = "$root_sha" ] || { echo "root check FAILED ($got): run the installer again" >&2; exit 1; }
|
|
echo " verified"
|
|
|
|
echo "== writing the boot image (p6)"
|
|
ow 'dd of=/dev/mmcblk0p6 bs=64k conv=fsync 2>/dev/null' < "$FIT"
|
|
got=$(ow "head -c $(stat -c %s "$FIT") /dev/mmcblk0p6 | sha256sum" </dev/null | cut -c1-64)
|
|
[ "$got" = "$(sha256sum < "$FIT" | cut -c1-64)" ] ||
|
|
{ echo "boot image check FAILED: run the installer again" >&2; exit 1; }
|
|
echo " verified"
|
|
|
|
echo "== rebooting into Guix"
|
|
ow 'sync; reboot' </dev/null || true
|
|
|
|
# Guix: wired ports = private network, 192.168.2.1, DHCP.
|
|
ip addr del $HOST/24 dev "$IFACE" 2>/dev/null || true
|
|
if [ -n "$nm_was_managed" ]; then
|
|
nmcli device set "$IFACE" managed yes; nm_was_managed=
|
|
echo " $IFACE is back under NetworkManager (DHCP)"
|
|
else
|
|
echo " give $IFACE an address by DHCP (or 192.168.2.2/24) to reach the router"
|
|
fi
|
|
echo
|
|
echo "Done. The first boot reboots once by itself (WiFi and TollGate setup);"
|
|
echo "about two minutes from now: ssh root@192.168.2.1"
|
|
echo "(with the key built into the release), LuCI at http://192.168.2.1:8080/."
|
|
echo "See INSTALL.md for the passwords to change and the WiFi networks."
|