Dev mode — no Cloudflare Access header present. Authenticated as dev@local.
EPL
dev@local

Lab 01 · Day 1, Session 1

Hardware Familiarization & Recovery

Duration: 60 minutes

Not started
Hardware profile:
IP: 192.168.8.1 Password: goodlife
Environment:

Lab 01 — Hardware Familiarization & Recovery

Duration: 60 minutes

You have a GL.iNet Mango (GL-MT300N-V2) running OpenWrt on a 16 MB NOR flash chip in front of you, and a VS Code Dev Container (or Codespace) hosting the engagement platform: a Debian operator console with an OpenWrt 23.05.3 rootfs available as a sibling container. By the end of this lab you will have SSH’d into the Mango, exec’d into the OpenWrt sibling, attached a serial console to the Mango, and exercised both software-level (failsafe) and bootloader-level (u-boot_mod HTTP) recovery paths.

The core lesson: you can break OpenWrt and recover it. Failsafe mode, U-Boot HTTP recovery (u-boot_mod), and serial console are not emergency last resorts. They are normal tools. Practice them before you need them.

Order matters here. Serial console is set up in Step 3, before the recovery exercises in Steps 6 and 7. Both recovery exercises rely on serial being attached. Do not skip ahead.

Where each step runs

This lab is marked codespaces: "partial" because about half the steps need physical access to your bench (Mango on local LAN, USB-UART adapter); the rest run fine in either a local Dev Container or a GitHub Codespace. Use this map to plan which terminal lives where.

StepBench laptop requiredOperator console (local Dev Container or Codespace)
1. Physical inspectionyesno
2. SSH to Mangoyes (Mango is on your local LAN)no
3. Serial consoleyes (USB-UART is local hardware)no
4. Dev Container + siblingnoyes
5. Side-by-side compareyes (Mango SSH from laptop)yes (sibling lives in operator console)
6. Failsafe via serialyesno
7. U-Boot HTTP recoveryyesno
Validationdepends (see below)depends (see below)

For Codespace students: keep your laptop terminal open for Mango SSH and serial work. The Codespace browser tab is your “operator console” and is where Steps 4 and 5’s sibling lives. Validation can run from either side; see the Validation section.


Learning objectives

  • Identify the Mango hardware features: ports, reset button, testpoints, USB.
  • Attach the serial console to the Mango (TP1–TP4) and observe boot.
  • SSH into the Mango, exec into an OpenWrt rootfs sibling container, and recognize that OpenWrt is OpenWrt regardless of the container/embedded boundary.
  • Execute OpenWrt failsafe deliberately (wrong config + recover via serial).
  • Understand u-boot_mod HTTP recovery (entry threshold, form metadata, no actual reflash).
  • Know the UART pinout and connection parameters cold.

Pre-state

Before starting this lab confirm all of the following.

Software on your laptop (always required for the bench-side work):

# A serial terminal client is installed (any one of these works)
command -v screen || command -v picocom || command -v minicom || command -v tio || \
    echo "Install one: sudo apt install screen  (or picocom / tio)"

# An SSH client (almost always present; install openssh-client if not)
command -v ssh

Operator console: pick one (use the Environment toggle at the top of this page to filter the rest of the lab to your chosen path):

  • Local Dev Container (recommended for the full course). Install Docker Desktop (or Podman Desktop, free and lighter on macOS or Windows) and the Dev Containers VS Code extension. Open this repo in VS Code and click “Reopen in Container.” First build takes 3 to 5 minutes; the pre-pulled OpenWrt rootfs sibling image takes about 15 MB on top.
  • Minimal local (Lab 01 only). Just docker, no VS Code, no Dev Container. Lab 01 only needs the OpenWrt rootfs sibling. If you don’t want to install the Dev Containers extension or build the full operator console yet, the bare path is two commands. You will need the full operator console starting in Lab 04 for wrangler, tailscale, and cloudflared, so plan accordingly.
    # Pull the rootfs once (~15 MB)
    docker pull openwrt/rootfs:x86-64-23.05.3
    
    # Skip Step 4a entirely; jump to Step 4b which boots this image directly.
    # Validate.sh and `docker exec ep-devcontainer ...` work the same.
    

Verify the engagement stack inside the operator console:

# These commands run inside the operator console (Debian shell), not on your laptop:
tailscale version           # the operator stack tools
cloudflared --version
wrangler --version
docker images openwrt/rootfs   # sibling image, pre-pulled by post-create.sh

Verify the rootfs is present on your laptop’s Docker daemon:

docker images openwrt/rootfs    # should show the x86-64-23.05.3 tag

Hardware on the bench (per student):

  • 1× GL.iNet Mango (GL-MT300N-V2) with DC power.
  • 1× Ethernet cable, laptop ↔ Mango LAN (black) port.
  • 3.3 V USB-to-UART adapter (FTDI, CP2102, CH340, or similar). Not a 5 V adapter. 5 V will damage the Mango PCB.
  • 4× dupont wires or probe clips.
  • USB drive (16 GB or larger, ext4-formattable). Used from Lab 03 onward; not strictly needed in Lab 01, but bring it.

Network reachability:

# Stock GL.iNet firmware default LAN is 192.168.8.1.
# If your Mango was pre-flashed with pure upstream OpenWrt, try 192.168.1.1.
ping -c 3 192.168.8.1

One-time setup after a fresh git clone:

chmod +x courses/engagement-platform-labs/labs/lab01-hardware-familiarization/validate.sh

NetworkManager users (most modern Linux distros): during Step 7 (HTTP recovery) you’ll need to assign a static 192.168.1.2/24 to your Ethernet interface while the Mango’s OpenWrt DHCP server is offline. NetworkManager will fight you on this. Be ready to run:

sudo nmcli device disconnect enp2s0       # substitute your interface name
sudo ip addr add 192.168.1.2/24 dev enp2s0
sudo ip link set enp2s0 up
# ...do recovery work...
sudo nmcli device connect enp2s0           # restore at end of step

Walkthrough

Power on the Mango and examine it while it boots.

Ports (approximate; exact arrangement varies by hardware revision, so verify against your unit):

  • USB-A port (typically on the side). The ExtRoot drive lives here in later labs.
  • Reset button (recessed). Held at power-on for 5 seconds or longer, triggers u-boot_mod HTTP recovery (Step 7). Reset is not used for OpenWrt failsafe; failsafe is entered via a serial keystroke (Step 6).
  • WAN (orange port). Uplink to an existing network.
  • LAN (black port). Direct device connection to your laptop.
  • DC barrel jack. 5 V / 1 A power input.

LED sequence during stock boot (approximate; verify against your firmware):

White, flashing quickly  → U-Boot / early kernel
White, flashing slowly   → OpenWrt init running
White, solid             → Normal operation

Later steps use the serial output as the authoritative timing reference. LED cadence is a useful at-a-glance signal but not the ground truth.

Dimensions. 58 mm × 58 mm × 22 mm, about 43 g. It fits in a coat pocket. The form factor is intentional for the drop-device role it plays from Lab 03 onward.

GPIO testpoints (used for serial in Step 3): TP1 through TP4 on the PCB near the SoC. You connect to these in Step 3; locate them visually now.

Connect your laptop’s Ethernet port to the LAN (black) port on the Mango. Set your laptop’s Ethernet interface to DHCP (or static 192.168.8.2/24).

Verify basic access (stock GL.iNet firmware, the factory default that students receive):

# Admin UI (GL.iNet stock web interface)
# Open in browser: http://192.168.8.1

# SSH. Stock GL.iNet firmware has SSH enabled on LAN by default.
# Stock GL.iNet default LAN: 192.168.8.1. Pure-OpenWrt default: 192.168.1.1.
# Default password: goodlife (printed on label; pure-OpenWrt has empty default, set with `passwd`)
ssh root@192.168.8.1

Pre-flashed and pure-OpenWrt units. If your Mango was previously flashed with upstream OpenWrt (e.g., an instructor’s reference unit, or a self-paced student who already ran Lab 02), the defaults differ:

  • LAN IP: 192.168.1.1 (not 192.168.8.1)
  • Root password: empty on first SSH (no GL.iNet goodlife default). Set one immediately with passwd, OR install your SSH pubkey via:
    mkdir -p /etc/dropbear && chmod 700 /etc/dropbear
    echo "$(cat ~/.ssh/id_ed25519.pub)" >> /etc/dropbear/authorized_keys
    chmod 600 /etc/dropbear/authorized_keys
    
  • Substitute 192.168.1.1 for 192.168.8.1 in every command in this lab.
  • This is the authoritative source for the MANGO_HOST env var; the Validation section just refers back here.

The rest of this lab assumes stock GL.iNet defaults; switch IPs/credentials as appropriate if you are on a pre-flashed unit.

Keep this SSH session open through Step 5. We’ll re-use it for the side-by-side comparison.

Codespace users: SSH to the Mango must be from your laptop terminal, not from the Codespace. The Mango lives on your local LAN (192.168.8.0/24 or 192.168.1.0/24); the Codespace is a remote VM that can’t see it directly. Lab 05 puts the Mango on a Tailscale tailnet. Once that’s done, the Codespace can reach the Mango by tailnet hostname. For Lab 01, use the laptop.

Once connected, gather system facts:

# What firmware are we on?
cat /etc/openwrt_release
# Stock GL.iNet (older, double-quoted):
#   DISTRIB_ID="OpenWrt"
#   DISTRIB_RELEASE="..."
# Pure upstream OpenWrt 23.05+ (single-quoted):
#   DISTRIB_ID='OpenWrt'
#   DISTRIB_RELEASE='23.05.x'
# Both forms are accepted by validate.sh.

# CPU and flash
cat /proc/cpuinfo | grep -E 'machine|system type'
cat /proc/mtd

# Memory
free -m

# Package counts (verified 2026-05-04):
#   Stock GL.iNet firmware (Mango):           ~150-200 (baseline + GL.iNet UI/services)
#   Upstream OpenWrt rootfs sibling (x86/64): ~127     (no GL.iNet additions; see Step 4)
#   Pure-OpenWrt Mango (after Lab 02 build):  ~140     (depends on selected packages)
opkg list-installed | wc -l

Key observation. Note the DISTRIB_TARGET field. It will say ramips/mt76x8. That target string drives the Mango sysupgrade image you build in Lab 02 with ImageBuilder.

Background reading. OpenWrt’s UCI is the unified config system that ties together /etc/config/*, /etc/init.d/*, and the uci CLI. It is the most important concept in the OpenWrt mental model, and you’ll touch it in every Mango-side lab. Skim it now if you haven’t met UCI before.

opkg is the package manager. The “Useful commands” section is all you need today; the full feed system shows up in Lab 02.

Serial gives you direct UART access to U-Boot and the kernel, earlier than any network or OpenWrt init. Attach it now because Step 6 (failsafe) and Step 7 (HTTP recovery) both depend on serial being present. If you skip this step, you will not be able to complete the recovery exercises.

Connection parameters:

Baud rate:    115200
Data bits:    8
Parity:       None
Stop bits:    1
Flow control: None

Verified UART pinout on the GL-MT300N-V2 PCB (testpoints TP1 through TP4):

TP1: 3.3V    (do NOT connect to your adapter's VCC unless the adapter requires it;
              typical USB-UART adapters self-power and TP1 should be left floating)
TP2: GND     -> adapter GND
TP3: TX      -> adapter RX  (this is what the Mango sends)
TP4: RX      -> adapter TX  (this is what the Mango receives)

Verified 2026-05-03 on the instructor’s GL-MT300N-V2 reference unit. Bidirectional serial confirmed at 115200 8N1. U-Boot, kernel, and OpenWrt login output all visible on TP3. If silk-screen labels on a particular unit are unclear, confirm GND with a multimeter (TP2 rings to the DC barrel jack ground ring) and identify TX activity during boot with a logic probe. Do not guess. Incorrect connections can damage the device or adapter.

Connect:

# Linux: identify your USB-to-UART adapter device
ls /dev/ttyUSB* /dev/ttyACM*
# Likely /dev/ttyUSB0 (FTDI/CP210x/CH340) or /dev/ttyACM0

# Attach with logging (so we can replay U-Boot / kernel output later if needed).
# screen's -L flag enables logging to the file specified by -Logfile.
screen -L -Logfile /tmp/mango-serial.log /dev/ttyUSB0 115200

# Alternatives: picocom (cleaner exit handling)  OR  tio (modern, log-friendly)
#   picocom -b 115200 /dev/ttyUSB0
#   tio --log /tmp/mango-serial.log -b 115200 /dev/ttyUSB0

# macOS
#   screen /dev/cu.usbserial-* 115200

screen exit: Ctrl+A then K then Y. screen detach (session keeps running): Ctrl+A then D, reattach with screen -r. Permission denied on /dev/ttyUSB0: add yourself to the dialout group and re-login, or sudo the command for the workshop.

Power-cycle the Mango with serial attached. You will see U-Boot messages, the kernel boot sequence, and the OpenWrt login prompt, all over the serial line. Notable lines to watch for (referenced again in Steps 6 and 7):

  • RESET button is pressed for: 0 second(s). U-Boot polling the reset line.
  • Autobooting in: 2 s (type 'gl' to run U-Boot console). 2-second U-Boot interrupt window.
  • Press the [f] key and hit [enter] to enter failsafe mode. OpenWrt failsafe entry banner, around 10 seconds of wall-clock from power-on.

If you see all three of these, your serial is working. Press Enter at the OpenWrt login prompt to confirm bidirectional input works.

Background reading. OpenWrt’s serial-console guide documents the full pinout discovery process for unfamiliar hardware. The u-boot_mod README (linked again in Step 7) covers the bootloader commands available at the gl ar7240> prompt (printenv, bootm, tftpboot, setenv, saveenv), useful if you need to escape an unbootable kernel without HTTP recovery.

The engagement-platform Dev Container is two-tier:

  • Outer container: Debian operator console. This is what VS Code (locally or in a Codespace) attaches to. It hosts the engagement stack: tailscale, cloudflared, wrangler, python, jq. Debian-based because vscode-server is a glibc binary, and OpenWrt’s musl userland cannot run it.
  • Sibling container: OpenWrt rootfs. Launched via docker-in-docker from the outer console. This is the soft-hardware companion to the Mango: the same OpenWrt 23.05.3 userland, on x86-64, with no flash constraint. Step 5’s comparison runs here.

From a shell prompt, the OpenWrt sibling is indistinguishable from the Mango except for target architecture and available storage. UCI, opkg, /etc/init.d, and procd are identical. The OS is the OS.

Minimal-local students: Step 4a (operator console attach) does not apply to you. There is no Debian outer to attach to. Skip ahead to Step 4b; the docker run invocation there is your entry point. Everything from Step 4b onward is identical regardless of which pre-state path you took.

Background reading. The Dev Containers specification is what devcontainer.json conforms to. The same file works for VS Code Dev Containers, GitHub Codespaces, IntelliJ Gateway, DevPod, and others. Codespaces is just a hosted runtime for that spec; see GitHub’s Codespaces docs for the lifecycle (auto-stop after 30 min idle, auto-delete after 30 days inactive, prebuilds, and so on).

4a. Open in VS Code (or Codespaces)

Local Dev Containers:

  1. Open VS Code in the course root:
    code /path/to/engagement-platform-labs
    
  2. VS Code detects .devcontainer/devcontainer.json and prompts “Reopen in Container.” Click it. (Requires the Dev Containers extension and a running Docker or Podman daemon.)
  3. The Debian operator console builds and starts. Open a new terminal (Ctrl+backtick); you are at a Debian shell:
    cat /etc/os-release | grep PRETTY_NAME
    # PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
    
    tailscale version    # confirms the operator stack is here
    wrangler --version
    

Codespaces (one-click, no local Docker required):

Click Open in Codespaces on the lab page header (or codespaces.new/errantpacket/epl-student-starter). The same Dockerfile builds; vscode-server runs natively on Debian.

4b. Launch the OpenWrt rootfs sibling

From a terminal inside the operator console, spin up the OpenWrt sibling in detached mode. The image has been pre-pulled by post-create.sh, so this returns instantly:

docker run -d --rm \
    --name ep-devcontainer \
    --hostname ep-devcontainer \
    openwrt/rootfs:x86-64-23.05.3 \
    /bin/sh -c 'mkdir -p /var/lock /var/run /var/log; tail -f /dev/null'

# Open a shell into the sibling for the rest of the lab:
openwrt-shell                          # convenience wrapper
# or, equivalently:
docker exec -it ep-devcontainer /bin/sh

Why detached: a foreground -it variant exits the container the moment you exit the shell, breaking Step 5 and validation. Detached + tail -f keeps PID 1 alive; docker exec lets you enter and exit shells freely.

Once inside the OpenWrt sibling:

cat /etc/openwrt_release
# DISTRIB_ID='OpenWrt'      (single-quoted; upstream 23.05.3 format)
# DISTRIB_TARGET='x86/64'   (the sibling is x86/64; the Mango is ramips/mt76x8)

opkg update
opkg list-installed | wc -l

Teaching moment. The bare upstream rootfs sibling has ~127 packages (verified 2026-05-04 on openwrt/rootfs:x86-64-23.05.3). The Mango on stock GL.iNet firmware has ~150 to 200. Both are roughly the same order of magnitude. The contrast is not count, it is what’s installed: the Mango adds GL.iNet’s web UI, vendor services, and additional drivers on top of the upstream baseline. In Lab 02 you build a custom image that strips the GL.iNet additions and adds the engagement stack you want.

Why not “~30 packages” like a minimal busybox image? Because openwrt/rootfs:x86-64-23.05.3 is the standard OpenWrt rootfs: busybox, opkg, dropbear, libuci, libubox, netifd, mtd, the procd init, default kernel modules, and the upstream package management plumbing. A “minimal” rootfs would mean rebuilding from imagebuilder with everything stripped, and that is a Lab 02 exercise, not a baseline.

When you’re done with the sibling for the lab session:

docker stop ep-devcontainer

Use the SSH session you opened in Step 2, plus a new shell into the OpenWrt sibling launched in Step 4b:

# Terminal 1: Mango (the SSH from Step 2)
ssh root@192.168.8.1   # stock GL.iNet; use 192.168.1.1 for pure-OpenWrt

# Terminal 2: OpenWrt sibling (running inside the operator console via DinD)
openwrt-shell
# or: docker exec -it ep-devcontainer /bin/sh

Run the same commands in both and note the differences:

# Architecture (CPU)
uname -m
# Mango: mips (or mipsel)   |  OpenWrt sibling: x86_64

# Kernel: interesting difference (containers share the host kernel)
uname -r
# Mango:   5.15.x          (OpenWrt 23.05.3's bundled kernel)
# Sibling: 6.8.0-azure ... (the Codespace VM's host kernel, or your laptop's)
#
# This is correct behavior. The OpenWrt sibling is just a userland packaged in
# a container, so it inherits whatever kernel the container engine is running
# on. UCI, opkg, and procd live in userland and don't care.

# Storage layout
df -h
# Mango:   /rom is ~3.5MB squashfs (read-only) + / is ~10MB overlayfs (writable)
# Sibling: / is the host's filesystem (effectively unbounded, TBs).
#          /rom and /overlay exist as empty stubs; there is no squashfs
#          beneath, so the failsafe-style overlay dance is a no-op here.

# OpenWrt version
cat /etc/openwrt_release | grep DISTRIB_RELEASE
# Both should land on 23.05.x by Lab 02 (this Lab 01 unit may still be 23.05.0
# pending Lab 02 sysupgrade; that's expected).

# UCI config system
uci show network
# Both have identical UCI syntax and semantics

The key observation for later labs: UCI, opkg, /etc/init.d, and procd are identical. A uci-defaults enrollment script written for the Mango runs unmodified in the OpenWrt sibling. The container/embedded contrast is architectural, not operational.

The Debian operator console outside the sibling runs a different shell (bash on glibc), but the labs that need OpenWrt semantics (UCI, opkg) always target the sibling or the real Mango. Day-2 cloud labs (Tailscale setup, wrangler deploys, cloudflared tunnels) run in the operator console directly; no sibling needed.

Bench-only: this step requires the laptop’s serial UART connection from Step 3. The Codespace can’t drive failsafe; run this from your laptop terminal alongside the Codespace browser tab.

Bench-only: this step requires the laptop’s serial UART connection from Step 3. The serial console is host-local hardware. Neither the Dev Container nor docker can pass through /dev/ttyUSB*, so run this from your laptop’s shell directly.

Failsafe mode bypasses all custom OpenWrt configuration and drops the device into a minimal shell. Use it when a bad config locks you out of the OS: wrong LAN IP, lost root password, broken /etc/init.d/network. The OS still boots; it’s the running configuration that’s wrong.

Important. Failsafe is entered over the serial console (UART) you attached in Step 3, not by holding the reset button. The reset-at-boot mechanism does something else on this hardware: see Step 7 (HTTP recovery via u-boot_mod).

Trigger a recoverable problem first (deliberate misconfig exercise):

# In your Step 2 SSH session into the Mango:
# Break the LAN IP so the laptop can no longer reach the Mango
uci set network.lan.ipaddr='10.99.99.1'
uci commit network
/etc/init.d/network restart
# SSH drops, as expected. Network access to the Mango is now dead.

Without serial attached, this is a brick. This exercise depends on the Step 3 serial console working. If you’re not yet attached over UART, stop and complete Step 3 first.

Enter failsafe over serial:

  1. Power-cycle the Mango (unplug + replug DC). Do not touch the reset button.
  2. Watch your serial terminal. The Linux kernel boots, then OpenWrt’s procd prints:
    Press the [f] key and hit [enter] to enter failsafe mode
    Press the [1], [2], [3] or [4] key and hit [enter] to select the debug level
    
    On a GL-MT300N-V2 this banner appears at kernel t≈6.5s, roughly 10 seconds of wall-clock time after power is applied.
  3. Within ~2.5 seconds (before mount_root: switching to jffs2 overlay prints), type f followed by Enter in your serial terminal.
  4. The boot diverges. Instead of the OpenWrt banner you land at a root@(none):/# prompt. The (none) hostname is the tell: you are in failsafe.

Repair the configuration:

# Mount the overlay so config edits persist
mount_root

# Fix the broken config
uci set network.lan.ipaddr='192.168.1.1'   # or 192.168.8.1 for stock GL.iNet
uci commit network

# Hard reboot back to normal mode
reboot -f

After the reboot, the Mango will go through a normal boot sequence and reach the OpenWrt login prompt. From your laptop, verify SSH works again:

ping -c 3 192.168.1.1
ssh root@192.168.1.1

Why this works. OpenWrt’s procd reads keystrokes from the kernel console (which is the UART) for about 2.5 seconds after procd: - early -. Pressing f flips procd into a failsafe init path that mounts only the read-only squashfs and skips /etc/init.d/*, /etc/uci-defaults/*, and the overlay. Failsafe also configures eth0 with 192.168.1.1/24 and starts a telnet listener, which is why some guides describe a network-only failsafe path. With serial in hand, network access isn’t required, and we don’t exercise it here.

Bench-only: this step assigns a static IP on your laptop’s Ethernet interface to talk to the Mango at 192.168.1.1. The Codespace cannot reach the Mango’s local LAN. Run this entirely from your laptop terminal.

Bench-only: this step assigns a static IP on your laptop’s Ethernet interface to talk to the Mango at 192.168.1.1. Configure the laptop NIC from the host shell, not from inside Docker or the Dev Container.

When the OS is broken to the point that it will not boot, failsafe is unreachable because no kernel is running. (Common causes: a failed sysupgrade, a corrupted kernel, or a partition table written incorrectly.) The next recovery layer lives in the bootloader. The Mango ships with u-boot_mod, a community-maintained U-Boot fork for Ralink and MediaTek SoCs that embeds an HTTP recovery server with a browser upload form.

This is u-boot_mod, not vendor stock U-Boot. GL.iNet ships u-boot_mod on most of their MT7628-class devices: Mango (MT300N-V2), Slate (AR300M), Creta (AR750), AR150, USB150, and others. The recovery form HTML credits https://github.com/pepe2k/u-boot_mod. Mainline OpenWrt’s bootloader recovery on devices without u-boot_mod is typically TFTP (UDP/69), not HTTP. Verified 2026-05-03 on a GL-MT300N-V2 running pure OpenWrt 23.05.

Trigger U-Boot HTTP recovery:

  1. Power off the Mango (unplug DC).
  2. Set your laptop’s Ethernet interface to a static 192.168.1.2/24 (the U-Boot HTTP server lives at 192.168.1.1):
    # NetworkManager users: release management before assigning manually
    sudo nmcli device disconnect enp2s0      # substitute your interface
    
    # Linux
    sudo ip addr add 192.168.1.2/24 dev enp2s0
    sudo ip link set enp2s0 up
    
    # macOS
    sudo ifconfig en0 192.168.1.2 netmask 255.255.255.0
    
  3. Press and hold the recessed reset button.
  4. While holding reset, plug power back in.
  5. Continue holding for at least 5 seconds (verified threshold). U-Boot counts the hold time and prints it on serial:
    RESET button is pressed for:  5 second(s)
    NetLoopHttpd, call eth_halt!
    HTTP server is starting at IP: 192.168.1.1
    HTTP server is ready!
    
  6. Release the reset button. The HTTP server is now running, autoboot is suppressed, and OpenWrt is NOT booting. The device sits in U-Boot until you upload a firmware image or power-cycle.

Verify the recovery server from your laptop:

# Confirm 200 OK and pull the upload form
curl -sS -o /tmp/recovery.html http://192.168.1.1/ && head -3 /tmp/recovery.html

# Or open in a browser:
xdg-open http://192.168.1.1/    # Linux
open http://192.168.1.1/         # macOS

The form metadata (verified):

<title>Firmware update</title>
<form method="post" enctype="multipart/form-data">
  <input type="file" name="firmware">
  <input type="submit" value="Update firmware">
</form>
  • Action: POST to /
  • Field name: firmware
  • Server-side validation: none. u-boot_mod writes whatever bytes you POST into the firmware partition, so choose your image carefully.
  • Browser-free upload (useful in Lab 12 for scripted flashing):
    curl -F 'firmware=@openwrt-23.05.3-ramips-mt76x8-glinet_gl-mt300n-v2-squashfs-sysupgrade.bin' http://192.168.1.1/
    

Do not upload anything in this lab. You are verifying mode entry only. Flashing is covered in Lab 03 (sysupgrade from a running OS) and Lab 12 (sealed image bake plus unattended provisioning).

Exit recovery: power-cycle the Mango (no button held). It will autoboot to OpenWrt normally. After it boots, restore NetworkManager control of the Ethernet interface:

sudo ip addr flush dev enp2s0
sudo nmcli device connect enp2s0

Confirm SSH to the Mango is back via DHCP’d address:

ssh root@192.168.8.1   # stock GL.iNet; use 192.168.1.1 for pre-flashed units

Threshold map (verified)

Reset hold durationU-Boot reportsAction
0 s (no button)0 second(s)Normal autoboot
~3 s< 5 second(s)”RESET button wasn’t pressed or not long enough”; normal autoboot
≥5 s≥5 second(s)HTTP recovery server starts at 192.168.1.1
Reset Button Hold Visualizer GL-MT300N-V2 / GL-SFT1200 U-Boot threshold table
LED: off (no button press)
U-Boot reports: (no button press)
Action: Normal autoboot — router starts normally.
Threshold table
0s Normal autoboot
1–4s Hold too short — autoboot
≥5s HTTP recovery at 192.168.1.1

There is also a serial-only path into U-Boot’s interactive console: type gl<Enter> during the 2-second Autobooting in: 2 s countdown. That drops you to the U-Boot prompt where you can run printenv, tftpboot, bootm, etc. The 2-second window is human-tight; spam-typing gl<Enter> repeatedly while the device boots improves reliability. Worth knowing exists; not the canonical recovery path.

GL.iNet HTTP vs mainline TFTP (for general knowledge)

AspectMainline OpenWrt U-Boot (e.g. some Belkin, RPi)u-boot_mod (Mango and most GL.iNet MT7628-class devices)
Recovery protocolTFTP (UDP/69)HTTP (TCP/80)
Host setuptftpd-hpa running, file in /srv/tftp/Just a browser, or curl -F
TriggerVaries by device (button, console interrupt)Reset held at power-on for ≥5 s
Default device IPVaries192.168.1.1
Upload mechanismTFTP get from device sideHTTP POST from host

If you encounter a non-GL.iNet OpenWrt device in the field, consult the OpenWrt wiki page for that specific model. Recovery semantics are determined by the vendor U-Boot, not by OpenWrt itself.

Post-state

When this lab is complete you should be able to answer yes to all of the following:

  • ssh root@192.168.8.1 (or 192.168.1.1 for pure-OpenWrt units) connects and shows the Mango shell prompt.
  • cat /etc/openwrt_release on the Mango shows DISTRIB_ID="OpenWrt" (or 'OpenWrt' on 23.05+ units, with single quotes; both accepted by validate.sh).
  • docker exec ep-devcontainer cat /etc/openwrt_release shows the same DISTRIB_ID.
  • You attached the serial console (Step 3) and observed U-Boot, kernel, and OpenWrt login output.
  • You successfully broke and recovered the Mango via failsafe mode (Step 6).
  • You confirmed u-boot_mod HTTP recovery is reachable (HTTP server is starting at IP: 192.168.1.1 printed on serial after a ≥5s reset-button-hold; the upload form rendered when browsed at http://192.168.1.1/).
  • You know the four testpoint labels on the Mango PCB and the correct UART parameters.

Validation

Three validation paths. Pick whichever matches your setup. All three call the same validate.sh and post the result to the same backend; only the delivery channel differs.

Path 1: epl CLI (recommended from the operator console).

The epl-cli tool is symlinked to epl on PATH by post-create.sh. After running epl login once (paste a token from /profile), each lab is one command:

epl validate lab01-hardware-familiarization

Output streams to your terminal; pass/fail and a “Mark complete” event are posted automatically. Re-run as many times as you like; only the most recent result is recorded. The validation script SSHes to the Mango, so the laptop must be reachable from the operator console. This works in local Dev Containers, and in Codespaces from Lab 05 onward over the tailnet.

Path 2: direct script (bench laptop).

If you’re working on your laptop only, run the script directly:

bash courses/engagement-platform-labs/labs/lab01-hardware-familiarization/validate.sh

Or via the Makefile:

cd courses/engagement-platform-labs/labs
make validate-lab01-hardware-familiarization

The script exits 0 on success and prints the first failing assertion on failure.

Path 3: paste output into the widget.

Run validate.sh anywhere, copy the full output, and paste it into the widget below. Equivalent to Path 1 but manual; useful when neither the laptop nor the operator console can reach the site directly.

What the script checks:

  1. SSH into the Mango at $MANGO_HOST succeeds and /etc/openwrt_release contains DISTRIB_ID="OpenWrt" or DISTRIB_ID='OpenWrt' (either quote style is accepted).
  2. docker exec ep-devcontainer cat /etc/openwrt_release succeeds and contains the same DISTRIB_ID.

Pre-flashed pure-OpenWrt Mangos. Override the IP per the Step 2 note: MANGO_HOST=192.168.1.1 ./validate.sh (or MANGO_HOST=192.168.1.1 epl validate lab01-hardware-familiarization).

Validate Output Paste your validate.sh output below

Take-home extension

See take-home/README.md for a pointer to the MT3000 tour content.


Primitives and drop-in substitutes

The Mango ships with u-boot_mod and OpenWrt failsafe, but the recovery primitive itself is older than either. Every embedded device has a layered recovery story: a bootloader that can be entered before the OS runs, a watchdog or fallback path baked into the OS itself, and a hardware-level interface (UART, JTAG) that bypasses both. Once you understand it that way, substituting the per-vendor specifics is mostly a matter of looking up which path each layer uses on the new hardware.

NameLayerWhere it shipsWhen to consider it
Mainline U-Boot + TFTP recoverybootloadermost non-GL.iNet OpenWrt devices, RPi-class boardsthe canonical alternative when u-boot_mod is absent; expects a TFTP server (tftpd-hpa) on the host LAN
Vendor stock recovery web UIsbootloader / OSmost consumer routers (TP-Link, ASUS, Netgear)first thing to try on unfamiliar hardware; trigger and host setup documented in the vendor manual
JTAG / SWD + OpenOCDsub-bootloadermost ARM SoCs, some MIPSwhen serial is dead or the bootloader itself is corrupted; needs a debug probe (Tigard, J-Link, or Black Magic Probe)
esptool.pybootloaderEspressif ESP32 / ESP8266 familythe u-boot_mod equivalent on a different SoC family; ROM boot mode over USB or UART
rkdeveloptoolbootloaderRockchip SoCs (RK3399, RK3588)for SBC-class hardware; loads images over USB in maskrom / bootrom mode
kwbootbootloaderMarvell Armada / Kirkwood (older NAS hardware)drops a replacement U-Boot over UART when even the existing bootloader is gone
OpenWrt failsafe (network path)OSevery OpenWrt devicethe telnet-on-192.168.1.1 path Step 6 documents but does not exercise; useful when serial is unavailable and only a network connection is at hand
Raspberry Pi bootcode / RPIBOOTbootloaderRaspberry Pi (all models with BCM283x or RP1)USB-connected bootrom mode; can load a full image from a host PC when the SD card or eMMC is unreadable

Take-home prompt

Pick a non-GL.iNet OpenWrt device from the OpenWrt Table of Hardware, find its device page, and document the equivalent of Lab 01’s Steps 6 and 7: what key or button triggers OS-layer failsafe, and what mechanism (HTTP, TFTP, serial console interrupt) the bootloader exposes for firmware recovery. Note the trigger threshold and the host-side requirements, and compare them to the Mango’s 5-second reset-hold and browser-accessible HTTP form.


Further reading

Lab 01’s surface is broad on purpose: recovery, hardware, and two operating environments. The links below let you go deeper on whichever piece interested you most.

OpenWrt fundamentals (skim these before Lab 02):

Hardware-specific:

Containers and the operator console:

Workshop-internal:

  • Lab 02: ImageBuilder Firmware. What comes next. You bake your own Mango image off the same ramips/mt76x8 baseline you saw in Step 2.
  • AUTHORING.md. For instructors and contributors. Documents how lab content, the site, the Worker, and the student-starter sync fit together.

View this page’s source: labs/lab01-hardware-familiarization/README.mdx


Troubleshooting

Cannot reach 192.168.8.1 (or 192.168.1.1 on pre-flashed units)
  • Confirm your Ethernet cable is in the LAN (black) port, not the WAN (orange) port.
  • Check that your laptop’s Ethernet interface received a DHCP address in the matching range: 192.168.8.0/24 for stock GL.iNet, 192.168.1.0/24 for pure OpenWrt. Use ip addr show (Linux) or ifconfig (macOS).
  • If you got a DHCP address in the other range (for example, you’re on 192.168.1.x but the Mango is at 192.168.8.1), the cable is likely in the WAN port. Ethernet traffic is reaching your home or office router, not the Mango. Move it to LAN.
  • If your interface is static, set it to 192.168.8.2/24 (stock) or 192.168.1.2/24 (pre-flashed) manually.
  • Check that the Mango LED is solid white (normal operation). If it is off or blinking abnormally, power-cycle the device.
  • Last resort: recover via Step 6 (failsafe over serial) or Step 7 (u-boot_mod HTTP recovery via a reset-button hold of 5 seconds or longer).
SSH connection refused on the Mango
  • Stock GL.iNet firmware enables SSH by default on LAN. If it was disabled, open the admin UI at http://192.168.8.1, go to System > Advanced > SSH Access, and enable it.
  • The default root password on stock GL.iNet firmware is printed on the device label (usually goodlife). If the password was changed, reset via failsafe mode (Step 6).
  • Pure-OpenWrt units have no root password set on first SSH. If SSH refuses with “Permission denied (publickey)”, the unit’s dropbear is configured key-only. Enter via serial and either run passwd or install your pubkey per the Step 2 note.
No /dev/ttyUSB* device after plugging in the UART adapter
  • Run dmesg | tail -20 immediately after plugging in. You should see a USB enumeration line and a cdc_acm, ftdi_sio, cp210x, or ch341 driver attaching. If nothing appears, the adapter may be incompatible (or unplugged).
  • The /dev/ttyUSB0 numbering can shift if you have multiple serial-USB devices. Check dmesg for the actual device path assigned.
  • If the device exists but screen says “Permission denied”, you’re not in the dialout group. sudo screen ... works for one-off, or sudo usermod -aG dialout $USER && newgrp dialout for permanent.
Failsafe mode not triggering (serial entry, Step 6)
  • Failsafe is entered over serial, not by holding the reset button. The reset button at power-on triggers u-boot_mod HTTP recovery (Step 7), which is a different recovery layer.
  • Watch your serial terminal for the line Press the [f] key and hit [enter] to enter failsafe mode. It appears at kernel t≈6.5s, roughly 10 seconds of wall-clock time after power-on.
  • The window before procd commits the boot is about 2.5 seconds. If you miss it, the OpenWrt login prompt prints normally; power-cycle and try again.
  • The failsafe shell prompt is root@(none):/#. The (none) hostname is the tell.
  • Run mount_root once inside failsafe to gain access to /etc/config/* for repair.
u-boot_mod HTTP recovery not triggering (Step 7)
  • Hold the reset button before applying power, and keep holding for at least 5 seconds after the LED comes on.
  • Confirm via serial: U-Boot prints RESET button is pressed for: N second(s). If N is < 5 you didn’t hold long enough; if N is 0 the button isn’t being registered (cable in the wrong port, button stuck, or similar).
  • Once HTTP server is starting at IP: 192.168.1.1 prints, set your laptop’s static IP to 192.168.1.2/24 (NetworkManager users: sudo nmcli device disconnect enp2s0 first, to keep it from clobbering the static address with DHCP retries).
  • Browse to http://192.168.1.1/. You should see the firmware-update form.
docker exec ep-devcontainer: container not found
  • The OpenWrt sibling must be running. From inside the operator console, verify with docker ps | grep ep-devcontainer.
  • If you launched the sibling in foreground (-it) mode, the container exited when you exited the shell. Re-run the Step 4b command using -d (detached) mode as documented, then openwrt-shell (or docker exec -it ep-devcontainer /bin/sh) for subsequent shells.
  • The sibling must be named ep-devcontainer for validate.sh to find it.
  • If docker itself is not found inside the operator console, the docker-in-docker feature did not install. Check .devcontainer/post-create.log and rebuild the Dev Container.
Validate output paster — available in Wave 2D (ValidateOutputPaster lab="lab01")
Downloadable artifacts for lab01 — served from R2 after Wave 3B deployment