#!/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' &2; exit 1; } ow 'grep -q glinet,gl-mt6000 /tmp/sysinfo/board_name 2>/dev/null || grep -q "GL-MT6000" /proc/device-tree/model' &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" &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" &2; exit 1; } echo " verified" echo "== rebooting into Guix" ow 'sync; reboot' /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."