The PC-Engines APU1D4 device that the Protectli VP2410 (aka ash) had replaced was still happily working. I’d continued to run FreeBSD on it, but came to a decision that it would be easier to maintain as a backup if it ran the same OS as the Protectli, i.e. Debian.

Creating an install image suitable for serial console

The first challenge was to create the Debian install media (a USB stick) so that it would work with a machine that only had a serial console. Of course, people had already done this, and this post was extremely helpful. To summarise:

  1. Download the preferred installation image (e.g. debian-12.9.0-amd64-netinst.iso).

  2. Mount the iso:

    sudo mount -o loop,ro debian-12.9.0-amd64-netinst.iso /mnt
  3. Extract the contents to local directory

    sudo cp -r /mnt tmp-iso
    sudo umount /mnt
  4. Modify three files in the tmp-iso directory

    1. isolinux/isolinux.cfg

      # D-I config version 2.0
      # search path for the c32 support libraries (libcom32, libutil etc.)
      serial 0 115200
      console 0
      path
      prompt 0
      timeout 0
      include menu.cfg
      default vesamenu.c32
    2. isolinux/txt.cfg

      label install
      	menu label ^Install
      	kernel /install.amd/vmlinuz
      #	append vga=788 initrd=/install.amd/initrd.gz --- quiet
              append priority=low vga=788 console=ttyS0,115200n8 initrd=/install.amd/initrd.gz --- console=ttyS0,115200n8
    3. isolinux/adtxt.cfg

      label expert
      	menu label E^xpert install
      	kernel /install.amd/vmlinuz
      #	append priority=low vga=788 initrd=/install.amd/initrd.gz ---
      	append priority=low vga=788 console=ttyS0,115200n8 initrd=/install.amd/initrd.gz --- console=ttyS0,115200n8
      include rqtxt.cfg
      label auto
      	menu label ^Automated install
      	kernel /install.amd/vmlinuz
      #	append auto=true priority=critical vga=788 initrd=/install.amd/initrd.gz --- quiet
      	append priority=low vga=788 console=ttyS0,115200n8 initrd=/install.amd/initrd.gz --- console=ttyS0,115200n8
  5. Create a new bootable ISO with the changed contents of tmp-iso. This is different to the command shown in the article, as that caused errors due to Joliet’s inability to handle symlinks.

    cd tmp-iso
    xorriso -as mkisofs -r -R -l -cache-inodes \
      -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin -partition_offset 16 \
      -A "Debian 12.9" -b isolinux/isolinux.bin -c isolinux/boot.cat \
      -no-emul-boot -boot-load-size 4 -boot-info-table \
      -o ~/tmp/debian-12.9-serial-install.iso .
  6. Write to a suitable USB drive

    sudo dd if=debian-12.9-serial-install.iso of=/dev/sdg bs=10M conv=sync
  7. Insert the USB stick and power-on the APU1D4, having connected a serial console.

Installing

Once the installer appears on the serial console, the process is mostly straightforward. Some points to note:

  • Use MBR partitioning scheme

  • Don’t install desktop software, but add web (aka apache2) and ssh.

  • Install GRUB on /dev/sda

  • After GRUB is installed, choose the shell option on the menu. Check /target/etc/grub/default has GRUB_TERMINAL = serial somewhere. If not, add it (using nano) and re-run the grub-install step.

Disk partitions

The 250GB SSD was partitioned as follows:

Mount

Size

Device

/boot

500MB

/dev/sda1

/

30GB

/dev/sda5

swap

4GB

/dev/sda6

/var

30GB

/dev/sda7

/tmp

4GB

/dev/sda8

/home

40GB

/dev/sda9

/rep

140GB

/dev/sda10

Packages

The following packages were installed once the system was installed and successfully rebooted:

  • emacs

  • make

  • gcc

  • rsync

  • rclone

  • tidy

  • sudo

  • bind9

  • hostapd

  • bridge-tools

  • isc-dhcp-server

  • dovecot

  • maildrop

  • fail2ban

  • logwatch

  • mpd

  • libbsd-dev and libncurses6-dev for mg

Notes

Setting up the bridge

The configuration in /etc/network/interfaces was similar to ash but with different network devices:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo br0
iface lo inet loopback

iface enp1s0 inet manual
iface enp2s0 inet manual
iface enp3s0 inet manual
iface wlp5s0 inet manual

iface br0 inet static
    bridge_ports    enp1s0 enp2s0 enp3s0 wlp5s0
    address         192.168.0.4
    broadcast       192.168.0.255
    netmask         255.255.255.0
    gateway         192.168.0.1
hostapd

The configuration file /etc/hostapd/hostapd.conf was also similar to ash:

interface=wlp5s0
# this next is key; tells hostapd that the wifi interface is part of a
# bridge (This has to be done via hostapd because wlps50 only becomes
# bridgeable when it's actually switched to "access point" mode.)
bridge=br0
channel=6
ieee80211n=1
hw_mode=g
ssid=SSID
wpa=2
wpa_passphrase=*redacted*
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
auth_algs=1
Notes

Hostapd really doesn’t like leading spaces in the hostapd.conf file directives.

Systemd unit for hostapd was masked (Debian policy apparently, so that the configuration can be written before enabling). To enable: systemctl unmask hostapd && systemctl enable hostapd.

Wireless was not part of the bridge until these issues were sorted.

python

Setup the default python version:

sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.11 5

Other apps

Configuration files for pretty much everything else was copied direct from ash.