Booting OpenBSD over TFTP in u-boot

I bought a RockPro64 v2.1 and have a TFTP server set up already. So how do I boot this machine with OpenBSD over the network?

Turns out just having bsd.rd isn't enough and you need another file, BOOTAA64.EFI, for booting the machine properly.

In my TFTP server, I ran the following commands:

cd /var/tftp
mkdir -p openbsd_aarch64snap
cd openbsd_aarch64snap
ftp https://cdn.openbsd.org/pub/OpenBSD/snapshots/arm64/BOOTAA64.EFI
ftp https://cdn.openbsd.org/pub/OpenBSD/snapshots/arm64/bsd.rd

Now I booted the RockPro64:

U-Boot TPL 2021.04-11556-g9ecacf77d2 (Jun 30 2021 - 17:48:07)
Channel 0: LPDDR4, 50MHz
BW=32 Col=10 Bk=8 CS0 Row=16/15 CS=1 Die BW=16 Size=2048MB
Channel 1: LPDDR4, 50MHz
BW=32 Col=10 Bk=8 CS0 Row=16/15 CS=1 Die BW=16 Size=2048MB
256B stride
lpddr4_set_rate: change freq to 400000000 mhz 0, 1
lpddr4_set_rate: change freq to 800000000 mhz 1, 0
Trying to boot from BOOTROM
Returning to boot ROM...

U-Boot SPL 2021.04-11556-g9ecacf77d2 (Jun 30 2021 - 17:48:07 +0000)
Trying to boot from SPI
NOTICE:  BL31: v2.5(release):v2.5
NOTICE:  BL31: Built : 17:45:54, Jun 30 2021


U-Boot 2021.04-11556-g9ecacf77d2 (Jun 30 2021 - 17:48:07 +0000)

SoC: Rockchip rk3399
Reset cause: RST
Model: Pine64 RockPro64 v2.1
DRAM:  3.9 GiB
PMIC:  RK808
MMC:   mmc@fe310000: 2, mmc@fe320000: 1, sdhci@fe330000: 0
Loading Environment from SPIFlash... SF: Detected gd25q128 with page size 256 Bytes, erase size 4 KiB, total 16 MiB
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial
Model: Pine64 RockPro64 v2.1
Net:   eth0: ethernet@fe300000
starting USB...
Bus usb@fe380000: USB EHCI 1.00
Bus usb@fe3a0000: USB OHCI 1.0
Bus usb@fe3c0000: USB EHCI 1.00
Bus usb@fe3e0000: USB OHCI 1.0
Bus dwc3: usb maximum-speed not found
Register 2000140 NbrPorts 2
Starting the controller
USB XHCI 1.10
scanning bus usb@fe380000 for devices... 1 USB Device(s) found
scanning bus usb@fe3a0000 for devices... 1 USB Device(s) found
scanning bus usb@fe3c0000 for devices... 1 USB Device(s) found
scanning bus usb@fe3e0000 for devices... 1 USB Device(s) found
scanning bus dwc3 for devices... 1 USB Device(s) found
       scanning usb for storage devices... 0 Storage Device(s) found
Hit any key to stop autoboot:  0
=>

Then check what the environment variables are available:

=> env print
altbootcmd=setenv boot_syslinux_conf extlinux/extlinux-rollback.conf;run distro_bootcmd
arch=arm
baudrate=1500000
board=rockpro64_rk3399
board_name=rockpro64_rk3399
boot_a_script=load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr}
boot_efi_binary=load ${devtype} ${devnum}:${distro_bootpart} ${kernel_addr_r} efi/boot/bootaa64.efi; if fdt addr ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r};else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi
boot_efi_bootmgr=if fdt addr ${fdt_addr_r}; then bootefi bootmgr ${fdt_addr_r};else bootefi bootmgr;fi
boot_extlinux=sysboot ${devtype} ${devnum}:${distro_bootpart} any ${scriptaddr} ${prefix}${boot_syslinux_conf}
boot_net_usb_start=usb start
boot_pci_enum=pci enum
boot_prefixes=/ /boot/
boot_script_dhcp=boot.scr.uimg
boot_scripts=boot.scr.uimg boot.scr
boot_syslinux_conf=extlinux/extlinux.conf
boot_targets=mmc0 mmc1 nvme0 scsi0 usb0 pxe dhcp sf0
bootcmd=run distro_bootcmd
bootcmd_dhcp=setenv devtype dhcp; run boot_net_usb_start; run boot_pci_enum; if dhcp ${scriptaddr} ${boot_script_dhcp}; then source ${scriptaddr}; fi;setenv efi_fdtfile ${fdtfile}; setenv efi_old_vci ${bootp_vci};setenv efi_old_arch ${bootp_arch};setenv bootp_vci PXEClient:Arch:00011:UNDI:003000;setenv bootp_arch 0xb;if dhcp ${kernel_addr_r}; then tftpboot ${fdt_addr_r} dtb/${efi_fdtfile};if fdt addr ${fdt_addr_r}; then bootefi ${kernel_addr_r} ${fdt_addr_r}; else bootefi ${kernel_addr_r} ${fdtcontroladdr};fi;fi;setenv bootp_vci ${efi_old_vci};setenv bootp_arch ${efi_old_arch};setenv efi_fdtfile;setenv efi_old_arch;setenv efi_old_vci;
bootcmd_mmc0=devnum=0; run mmc_boot
bootcmd_mmc1=devnum=1; run mmc_boot
bootcmd_nvme0=devnum=0; run nvme_boot
bootcmd_pxe=run boot_net_usb_start; run boot_pci_enum; dhcp; if pxe get; then pxe boot; fi
bootcmd_scsi0=devnum=0; run scsi_boot
bootcmd_sf0=busnum=0; run sf_boot
bootcmd_usb0=devnum=0; run usb_boot
bootdelay=0
cpu=armv8
cpuid#=5447465333332e3030000000000c0804
distro_bootcmd=scsi_need_init=; setenv nvme_need_init; for target in ${boot_targets}; do run bootcmd_${target}; done
efi_dtb_prefixes=/ /dtb/ /dtb/current/
ethaddr=2a:1b:e6:60:a8:03
fdt_addr_r=0x01f00000
fdtcontroladdr=f1f07310
fdtfile=rockchip/rk3399-rockpro64.dtb
kernel_addr_r=0x02080000
kernel_comp_addr_r=0x08000000
kernel_comp_size=0x2000000
load_efi_dtb=load ${devtype} ${devnum}:${distro_bootpart} ${fdt_addr_r} ${prefix}${efi_fdtfile}
mmc_boot=if mmc dev ${devnum}; then devtype=mmc; run scan_dev_for_boot_part; fi
nvme_boot=run boot_pci_enum; run nvme_init; if nvme dev ${devnum}; then devtype=nvme; run scan_dev_for_boot_part; fi
nvme_init=if ${nvme_need_init}; then setenv nvme_need_init false; nvme scan; fi
partitions=uuid_disk=${uuid_gpt_disk};name=loader1,start=32K,size=4000K,uuid=${uuid_gpt_loader1};name=loader2,start=8MB,size=4MB,uuid=${uuid_gpt_loader2};name=trust,size=4M,uuid=${uuid_gpt_atf};name=boot,size=112M,bootable,uuid=${uuid_gpt_boot};name=rootfs,size=-,uuid=B921B045-1DF0-41C3-AF44-4C6F280D3FAE;
preboot=usb start
pxefile_addr_r=0x00600000
ramdisk_addr_r=0x06000000
scan_dev_for_boot=echo Scanning ${devtype} ${devnum}:${distro_bootpart}...; for prefix in ${boot_prefixes}; do run scan_dev_for_extlinux; run scan_dev_for_scripts; done;run scan_dev_for_efi;
scan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done; setenv devplist
scan_dev_for_efi=setenv efi_fdtfile ${fdtfile}; for prefix in ${efi_dtb_prefixes}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${efi_fdtfile}; then run load_efi_dtb; fi;done;run boot_efi_bootmgr;if test -e ${devtype} ${devnum}:${distro_bootpart} efi/boot/bootaa64.efi; then echo Found EFI removable media binary efi/boot/bootaa64.efi; run boot_efi_binary; echo EFI LOAD FAILED: continuing...; fi; setenv efi_fdtfile
scan_dev_for_extlinux=if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${boot_syslinux_conf}; then echo Found ${prefix}${boot_syslinux_conf}; run boot_extlinux; echo SCRIPT FAILED: continuing...; fi
scan_dev_for_scripts=for script in ${boot_scripts}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script}; then echo Found U-Boot script ${prefix}${script}; run boot_a_script; echo SCRIPT FAILED: continuing...; fi; done
scan_sf_for_scripts=${devtype} read ${scriptaddr} ${script_offset_f} ${script_size_f}; source ${scriptaddr}; echo SCRIPT FAILED: continuing...
script_offset_f=0xffe000
script_size_f=0x2000
scriptaddr=0x00500000
scsi_boot=run boot_pci_enum; run scsi_init; if scsi dev ${devnum}; then devtype=scsi; run scan_dev_for_boot_part; fi
scsi_init=if ${scsi_need_init}; then scsi_need_init=false; scsi scan; fi
serial#=2f25948cb9727e4e
sf_boot=if sf probe ${busnum}; then devtype=sf; run scan_sf_for_scripts; fi
soc=rk3399
stderr=serial,vidconsole
stdin=serial,usbkbd
stdout=serial,vidconsole
usb_boot=usb start; if usb dev ${devnum}; then devtype=usb; run scan_dev_for_boot_part; fi
vendor=pine64

Environment size: 5128/32764 bytes

You want to look for things which seem like a kernel loading address, which in the above output is kernel_addr_r. We will load the EFI bootloader into that address.

First enable DHCP and get an IP address. In my case, I had the default boot file set to pxelinux.0 in the DHCP server, so it was auto loaded. That is safe to ignore as we will load BOOTAA64.EFI and overwrite pxelinux.0.

=> dhcp
Speed: 1000, full duplex
BOOTP broadcast 1
BOOTP broadcast 2
DHCP client bound to address 192.168.2.244 (257 ms)
Using ethernet@fe300000 device
TFTP from server 192.168.2.1; our IP address is 192.168.2.244
Filename 'pxelinux.0'.
Load address: 0x800800
Loading: ##T ##
         8.8 KiB/s
done
Bytes transferred = 46995 (b793 hex)
=> tftp ${kernel_addr_r} openbsd_aarch64snap/BOOTAA64.EFI
Speed: 1000, full duplex
Using ethernet@fe300000 device
TFTP from server 192.168.2.1; our IP address is 192.168.2.244
Filename 'openbsd_aarch64snap/BOOTAA64.EFI'.
Load address: 0x2080000
Loading: ################
         9.7 MiB/s
done
Bytes transferred = 233644 (390ac hex)

Now boot that file using the EFI boot command bootefi:

=> bootefi ${kernel_addr_r}
Card did not respond to voltage select! : -110
Scanning disk mmc@fe310000.blk...
Disk mmc@fe310000.blk not ready
Scanning disk mmc@fe320000.blk...
No valid Btrfs found
** Unrecognized filesystem type **
Card did not respond to voltage select! : -110
Scanning disk sdhci@fe330000.blk...
Disk sdhci@fe330000.blk not ready
Found 2 disks
No EFI system partition
Booting /openbsd_aarch64snap\BOOTAA64.EF
disks: sd0
>> OpenBSD/arm64 BOOTAA64 1.20
Speed: 1000, full duplex
Speed: 1000, full duplex
Thu Jan  1 00:00:00 1970
boot>

Now we are in the OpenBSD EFI bootloader and need to do another TFTP boot to get the bsd.rd kernel:

boot> boot tftp0a:openbsd_aarch64snap/bsd.rd
Speed: 1000, full duplex
booting tftp0a:openbsd_aarch64snap/bsd.rd: Speed: 1000, full duplex
3082744+1254728+12713376+642976 [274423+91+713232+292465]=0x13f5920
Copyright (c) 1982, 1986, 1989, 1991, 1993
        The Regents of the University of California.  All rights reserved.
Copyright (c) 1995-2025 OpenBSD. All rights reserved.  https://www.OpenBSD.org

OpenBSD 7.6-current (RAMDISK) #308: Wed Feb 26 14:08:23 MST 2025
    deraadt@arm64.openbsd.org:/usr/src/sys/arch/arm64/compile/RAMDISK
real mem  = 4038643712 (3851MB)
avail mem = 3873263616 (3693MB)
random: good seed from bootblocks
mainbus0 at root: Pine64 RockPro64 v2.1
psci0 at mainbus0: PSCI 1.1, SMCCC 1.2, SYSTEM_SUSPEND
efi0 at mainbus0: UEFI 2.8
efi0: Das U-Boot rev 0x20210400
smbios0 at efi0: SMBIOS 3.0
smbios0: vendor U-Boot version "2021.04-11556-g9ecacf77d2" date 06/30/2021
cpu0 at mainbus0 mpidr 0: ARM Cortex-A53 r0p4
cpu0: 32KB 64b/line 2-way L1 VIPT I-cache, 32KB 64b/line 4-way L1 D-cache
cpu0: 512KB 64b/line 16-way L2 cache
cpu0: CRC32,SHA2,SHA1,AES+PMULL,ASID16
agintc0 at mainbus0 sec shift 3:3 nirq 288 nredist 6: "interrupt-controller"
agintcmsi0 at agintc0
syscon0 at mainbus0: "qos"
syscon1 at mainbus0: "qos"
syscon2 at mainbus0: "qos"
syscon3 at mainbus0: "qos"
syscon4 at mainbus0: "qos"
syscon5 at mainbus0: "qos"
syscon6 at mainbus0: "qos"
syscon7 at mainbus0: "qos"
syscon8 at mainbus0: "qos"
syscon9 at mainbus0: "qos"
syscon10 at mainbus0: "qos"
syscon11 at mainbus0: "qos"
syscon12 at mainbus0: "qos"
syscon13 at mainbus0: "qos"
syscon14 at mainbus0: "qos"
syscon15 at mainbus0: "qos"
syscon16 at mainbus0: "qos"
syscon17 at mainbus0: "qos"
syscon18 at mainbus0: "qos"
syscon19 at mainbus0: "qos"
syscon20 at mainbus0: "qos"
syscon21 at mainbus0: "qos"
syscon22 at mainbus0: "qos"
syscon23 at mainbus0: "qos"
syscon24 at mainbus0: "qos"
syscon25 at mainbus0: "power-management"
"power-controller" at syscon25 not configured
syscon26 at mainbus0: "syscon"
"io-domains" at syscon26 not configured
rkclock0 at mainbus0
rkclock1 at mainbus0
CRU_CLKGATE_CON6: 0x00000060
CRU_CLKGATE_CON13: 0x000000c0
syscon27 at mainbus0: "syscon"
rkusbphy0 at syscon27: phy 0
rkusbphy1 at syscon27: phy 1
"io-domains" at syscon27 not configured
rkemmcphy0 at syscon27
"pcie-phy" at syscon27 not configured
rktcphy0 at mainbus0
rktcphy1 at mainbus0
rkpinctrl0 at mainbus0: "pinctrl"
rkgpio0 at rkpinctrl0
rkgpio1 at rkpinctrl0
rkgpio2 at rkpinctrl0
rkgpio3 at rkpinctrl0
rkgpio4 at rkpinctrl0
syscon28 at mainbus0: "syscon"
syscon29 at mainbus0: "syscon"
"fit-images" at mainbus0 not configured
"display-subsystem" at mainbus0 not configured
"pmu_a53" at mainbus0 not configured
"pmu_a72" at mainbus0 not configured
agtimer0 at mainbus0: 24000 kHz
"xin24m" at mainbus0 not configured
simplebus0 at mainbus0: "bus"
"dma-controller" at simplebus0 not configured
"dma-controller" at simplebus0 not configured
rkpcie0 at mainbus0
rkpcie0: link training timeout
dwge0 at mainbus0: rev 0x35, address 2a:1b:e6:60:a8:03
rgephy0 at dwge0 phy 0: RTL8169S/8110S/8211, rev. 6
dwmmc0 at mainbus0: 50 MHz base clock
sdmmc0 at dwmmc0: 4-bit, sd high-speed, dma
dwmmc1 at mainbus0: 50 MHz base clock
sdmmc1 at dwmmc1: 4-bit, sd high-speed, dma
sdhc0 at mainbus0
sdhc0: SDHC 3.00, 200 MHz base clock
sdmmc2 at sdhc0: 8-bit, sd high-speed, mmc high-speed, dma
ehci0 at mainbus0
usb0 at ehci0: USB revision 2.0
uhub0 at usb0 configuration 1 interface 0 "Generic EHCI root hub" rev 2.00/1.00 addr 1
ohci0 at mainbus0: version 1.0
ehci1 at mainbus0
usb1 at ehci1: USB revision 2.0
uhub1 at usb1 configuration 1 interface 0 "Generic EHCI root hub" rev 2.00/1.00 addr 1
ohci1 at mainbus0: version 1.0
rkdwusb0 at mainbus0: "usb"
xhci0 at rkdwusb0, xHCI 1.10
usb2 at xhci0: USB revision 3.0
uhub2 at usb2 configuration 1 interface 0 "Generic xHCI root hub" rev 3.00/1.00 addr 1
rkdwusb1 at mainbus0: "usb"
xhci1 at rkdwusb1, xHCI 1.10
usb3 at xhci1: USB revision 3.0
uhub3 at usb3 configuration 1 interface 0 "Generic xHCI root hub" rev 3.00/1.00 addr 1
"saradc" at mainbus0 not configured
rkiic0 at mainbus0
iic0 at rkiic0
"everest,es8316" at iic0 addr 0x11 not configured
rkiic1 at mainbus0
iic1 at rkiic1
com0 at mainbus0: dw16550, 64 byte fifo
com1 at mainbus0: dw16550, 64 byte fifo
com1: console
rkspi0 at mainbus0
"tsadc" at mainbus0 not configured
rkiic2 at mainbus0
iic2 at rkiic2
rkpmic0 at iic2 addr 0x1b: RK808
"silergy,syr827" at iic2 addr 0x40 not configured
"silergy,syr828" at iic2 addr 0x41 not configured
rkiic3 at mainbus0
iic3 at rkiic3
fusbtc0 at iic3 addr 0x22
rkpwm0 at mainbus0
rkpwm1 at mainbus0
rkpwm2 at mainbus0
"video-codec" at mainbus0 not configured
"iommu" at mainbus0 not configured
"rga" at mainbus0 not configured
"efuse" at mainbus0 not configured
"watchdog" at mainbus0 not configured
"rktimer" at mainbus0 not configured
"i2s" at mainbus0 not configured
"i2s" at mainbus0 not configured
"i2s" at mainbus0 not configured
"vop" at mainbus0 not configured
"iommu" at mainbus0 not configured
"vop" at mainbus0 not configured
"iommu" at mainbus0 not configured
"iommu" at mainbus0 not configured
"iommu" at mainbus0 not configured
"hdmi-sound" at mainbus0 not configured
"hdmi" at mainbus0 not configured
"gpu" at mainbus0 not configured
"opp-table0" at mainbus0 not configured
"opp-table1" at mainbus0 not configured
"opp-table2" at mainbus0 not configured
"external-gmac-clock" at mainbus0 not configured
"gpio-keys" at mainbus0 not configured
"leds" at mainbus0 not configured
pwmfan0 at mainbus0: no cooling levels
"sdio-pwrseq" at mainbus0 not configured
"sound" at mainbus0 not configured
"vcc12v-dcin" at mainbus0 not configured
"vcc1v8-s3" at mainbus0 not configured
"vcc3v3-pcie-regulator" at mainbus0 not configured
"vcc3v3-sys" at mainbus0 not configured
"vcc5v0-host-regulator" at mainbus0 not configured
"vcc5v0-typec-regulator" at mainbus0 not configured
"vcc5v0-sys" at mainbus0 not configured
"vcc5v0-usb" at mainbus0 not configured
"vdd-log" at mainbus0 not configured
"binman" at mainbus0 not configured
"dfi" at mainbus0 not configured
rkrng0 at mainbus0: ver 1
"dmc" at mainbus0 not configured
"config" at mainbus0 not configured
usb4 at ohci0: USB revision 1.0
uhub4 at usb4 configuration 1 interface 0 "Generic OHCI root hub" rev 1.00/1.00 addr 1
usb5 at ohci1: USB revision 1.0
uhub5 at usb5 configuration 1 interface 0 "Generic OHCI root hub" rev 1.00/1.00 addr 1
sdmmc0: can't enable card
scsibus0 at sdmmc1: 2 targets, initiator 0
sd0 at scsibus0 targ 1 lun 0: <Sandisk, SN128, 0086> removable
sd0: 121942MB, 512 bytes/sector, 249737216 sectors
sdmmc2: can't enable card
softraid0 at root
scsibus1 at softraid0: 256 targets
root on rd0a swap on rd0b dump on rd0b
erase ^?, werase ^W, kill ^U, intr ^C, status ^T

Welcome to the OpenBSD/arm64 7.6 installation program.
Starting non-interactive mode in 5 seconds...
(I)nstall, (U)pgrade, (A)utoinstall or (S)hell? s
#

And it should be easy to proceed from here.

Fast positions in left-complete, array based binary trees

A recent question I came across when working with static left complete binary search trees.

  • How to quickly calculate the position of the k'th element in the sorted order?

Suppose our search tree is as follows

nodes = [ 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 ]

              8
      4               12
  2       6       10      14
1   3   5   7   9   11  13  15

Then the position of the 3'rd element is 9, position of 7th element is 11.

Now lets modify the question a bit further.

Calculate the position of the k'th element in the sorted order, for the subtree at position p and height h

k_child(p, h, k)

So for the subtree at position 3 (the element 12), and height 2 (a tree with a single node has height 0), the 3'rd element is at position 13 ( the element 11).
For p=3, h=1, k=3, the 3'rd element is at position 7.

Generally this is done using a while loop and the fact that the left and right child positions can be calculated by 2*p, 2*p+1 and takes O(b^2), where b is the number of bits in the numbers we are working with (typically 32 bit integers)

We can do a lot better, in O(b) as shown with the following C++ code snippet

#define k_child(p, h, k)        (((p<<h)|(k>>1))/(k&(~(k-1))))

Note: The division will be optimized to bit shifts as we are dividing by a power of 2.

It's a cute little puzzle which takes a bit of time to figure out but hopefully putting this out there will help others too :D

man.cgi with lighttpd

This post describes setting up OpenBSDs man.cgi(8) with lighttpd on Gentoo, with OpenRC.

Reasons for choosing lighttpd over nginx:

  • Native CGI binary support
  • Native chroot support

Both of these are going to turn out to be very important for man.cgi(8).

Installing man.cgi

First step would be to install a working man.cgi static binary. For this, we need to install to app-text/mandoc with the cgi USE flag:

$ USE="cgi" emerge --ask --verbose app-text/mandoc

This will install a static binary /var/www/cgi-bin/man.cgi.

Installing lighttpd

The default flags for lighttpd are enough to bring in CGI wrapper support.

$ emerge --ask --verbose www-servers/lighttpd

Set up /etc/lighttpd/lighttpd.conf

I am going to assume that this server is only for serving man pages and for nothing else, in which case the following /etc/lighttpd/lighttpd.conf should suffice:

# chroot to directory (defaults to no chroot)
server.chroot            = "/var/www"

server.username          = "lighttpd"
server.groupname         = "lighttpd"

var.basedir              = "/"
var.logdir               = "/var/log/lighttpd"
var.statedir             = "/var/lib/lighttpd"

server.errorlog          = var.logdir + "/error.log"
accesslog.filename       = var.logdir + "/access.log"

server.document-root     = "/"
server.pid-file          = "/var/run/lighttpd.pid"

# NOTE: the order of modules is important.
server.modules = (
    "mod_access",
    "mod_cgi",
    "mod_accesslog"
)

server.indexfiles        = ("index.php", "index.html",
                            "index.htm", "default.htm")

server.follow-symlink    = "enable"

server.use-ipv6          = "enable"

# (extensions that are usually handled by mod_cgi, mod_fastcgi, etc).
static-file.exclude-extensions = (".php", ".pl", ".cgi", ".fcgi")

$HTTP["url"] =~ "^/cgi-bin/" {
	cgi.assign = (
		".cgi"   => "/cgi-bin/man.cgi"
	)
}

include "mime-types.conf"

Don't start the server yet, it will fail

Set up the /var/www chroot

From the above configuration, it is clear that we need some extra directories in /var/www and with proper permissions.
I am going to be a bit lax about permissions, they can be hardened if need be.

$ mkdir -p /var/www/var/{{lib,log}/lighttpd,run,tmp} /var/www/man/system /var/www/dev
$ chown -R lighttpd:lighttpd /var/www/var
$ chmod 777 /var/www/var/{run,tmp}

Set up the /etc/init.d/lighttpd service

Lighttpd currently needs a /dev/null to be present inside the chroot at /var/www which is not there by default.
We will modify the lighttpd init.d file to mount a devtmpfs on /var/www/dev at each start.
You can of course do it a different way; make it into a separate init, modify existing devtmpfs mounts, etc. This is good enough for local servers.

#!/sbin/openrc-run
# Copyright 1999-2016 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

extra_started_commands="reload graceful"

LIGHTTPD_PID="$(grep pid ${LIGHTTPD_CONF} | cut -d '=' -f 2 | tr -d \\\" | tr -d [:space:])"

depend() {
	need net
	use mysql logger spawn-fcgi ldap slapd netmount dns
	after famd
	after sshd
}

checkconfig() {
	if [ ! -f "${LIGHTTPD_CONF}" ] ; then
		ewarn "${LIGHTTPD_CONF} does not exist."
		return 1
	fi

	if [ -z "${LIGHTTPD_PID}" ] ; then
		eerror "server.pid-file variable in ${LIGHTTPD_CONF}"
		eerror "is not set. Please set this variable properly"
		eerror "and try again"
		return 1
	fi
	/usr/sbin/lighttpd -t -f ${LIGHTTPD_CONF}
}

start() {
	checkconfig || return 1
	# Glean lighttpd's credentials from the configuration file
	# Fixes bug 454366
	LIGHTTPD_USER="$(awk '/^server.username/{s=$3};{sub("\"","",s)};END{print s}' ${LIGHTTPD_CONF})"
	LIGHTTPD_GROUP="$(awk '/^server.groupname/{s=$3};{sub("\"","",s)};END{print s}' ${LIGHTTPD_CONF})"
	checkpath -d -q -m 0750 -o "${LIGHTTPD_USER}":"${LIGHTTPD_GROUP}" /run/lighttpd/
	
	mount -t devtmpfs none /var/www/dev

	ebegin "Starting lighttpd"
	start-stop-daemon --start --exec /usr/sbin/lighttpd \
		--pidfile "${LIGHTTPD_PID}" -- -f "${LIGHTTPD_CONF}"
	eend $?
}

stop() {
	local rv=0
	ebegin "Stopping lighttpd"
	start-stop-daemon --stop --pidfile "${LIGHTTPD_PID}"
	umount -R /var/www/dev
}

reload() {
	if ! service_started "${SVCNAME}" ; then
		eerror "${SVCNAME} isn't running"
		return 1
	fi
	checkconfig || return 1

	ebegin "Re-opening lighttpd log files"
	start-stop-daemon --pidfile "${LIGHTTPD_PID}" \
		--signal HUP
	eend $?
}

graceful() {
	if ! service_started "${SVCNAME}" ; then
		eerror "${SVCNAME} isn't running"
		return 1
	fi
	checkconfig || return 1

	ebegin "Gracefully stopping lighttpd"
	start-stop-daemon --quiet --pidfile "${LIGHTTPD_PID}" \
		--signal INT
	if eend $? ; then
		rm -f "${LIGHTTPD_PID}"
		start
	fi
}

Enable and start lighttpd.

$ mkdir -p /var/www/dev
$ rc-update add lighttpd default
$ rc-service lighttpd start

Set up the man pages

We will copy all the man pages into /var/www/man/system and be done with the whole setup.

$ cp -r /usr/share/man/.  /var/www/man/system
$ cd /var/www/man/system
$ makewhatis -T utf8 .
$ echo "system" > /var/www/man/manpath.conf

AND WE ARE DONE

Visit http://localhost/cgi-bin/man.cgi/ to check out them sexy man pages.

OpenBSD backups with restic and /etc/daily.local

This is yet another one of the guides on how to do backups for a server, this time with restic and OpenBSD. This sets up a daily backup and mails the changes to root.

Install restic

This is the simplest part:

$ pkg_add restic

Setup a restic repository

From the official instructions, the example here sets up an sftp repository over ssh. For this to work, root must have access to the backup server using its ssh key.

$ cat /root/.ssh/config
Host truenas
	HostName 192.168.4.2
	User restic
	IdentityFile /root/.ssh/id_ed25519

$ restic --repo sftp:truenas:/mnt/Media/backups/fwall init

Store the password that was used in plain text in /root/.ssh/restic:

$ ls -lh /root/.ssh/restic
-rw-------  1 root  wheel    18B Dec 24 20:32 /root/.ssh/restic

$ cat /root/.ssh/restic
SomeReallyCoolResticPassword

NOTE: Make sure that the ssh user, restic, has full access to the backup folder.

Setup /etc/daily.local

Create a new file /etc/daily.local.restic with the contents:

# backup restic
R_FILE="/root/.ssh/restic"
R_REPO="sftp:truenas:/mnt/Media/backups/fwall"
env RESTIC_PASSWORD_FILE="${R_FILE}" \
HOME="/root" \
/usr/local/bin/restic --repo ${R_REPO} \
	--verbose backup \
	--exclude-if-present=no_restic \
	--exclude-file=/etc/restic.exclude \
	--files-from=/etc/restic.include \
	--tag="$(date +%c)"

# list changes
PREV=$(env RESTIC_PASSWORD_FILE="${R_FILE}" HOME="/root" \
	/usr/local/bin/restic --repo ${R_REPO} \
	snapshots --compact | tail -4 | head -1 | awk '{print $1}')
LAST=$(env RESTIC_PASSWORD_FILE="${R_FILE}" HOME="/root" \
	/usr/local/bin/restic --repo ${R_REPO} \
	snapshots --compact | tail -3 | head -1 | awk '{print $1}')

RDIFF_FILE="$(mktemp)"
env RESTIC_PASSWORD_FILE="${R_FILE}" HOME="/root" \
	/usr/local/bin/restic --repo ${R_REPO} \
	diff ${PREV} ${LAST} > ${RDIFF_FILE}

NLINES=$(wc -l "${RDIFF_FILE}" | awk '{print $1}')
if [ ${NLINES} -gt 108 ] ; then
	head -n 100 "${RDIFF_FILE}"
	printf "======= SNIP ======\n"
	tail -n 8 "${RDIFF_FILE}"
else
	cat "${RDIFF_FILE}"
fi
rm -f "${RDIFF_FILE}"
unset R_REPO R_FILE RDIFF_FILE NLINES

And in the /etc/daily.local, add a line which sources /etc/daily.local.restic:

...
{other personal scripts}
...
. /etc/daily.local.restic

This sets up the daily script, which backs up the whole system. We still need to create the config files /etc/restic.include and /etc/restic.exclude.

/etc/restic.include:

/etc
/root
/home
/usr
/bin
/sbin
/var

/etc/restic.exclude:

/var/run
/var/spool
/var/tmp

Any folder which contains a file named no_restic is also excluded. For example, if a user has multiple git repositories in /home/aisha/GIT/ and they wish to avoid backups of this folder, create a file /home/aisha/GIT/no_restic, an empty file is fine, it can also contain the reason for excluding this folder.

$ locate no_restic
/home/aisha/GIT/no_restic

$ cat /home/aisha/GIT/no_restic
no need to do backups of online version controlled folders

Test backup

To test it out, first do a full back of the system:

$ env -i R_FILE="/root/.ssh/restic" \
    R_REPO="sftp:truenas:/mnt/Media/backups/fwall" \
    RESTIC_PASSWORD_FILE="${R_FILE}" \
    HOME="/root" \
    /usr/local/bin/restic --repo ${R_REPO} \
    --verbose backup \
    --exclude-if-present=no_restic \
    --exclude-file=/etc/restic.exclude \
    --files-from=/etc/restic.include \
    --tag="$(date +%c)"

Now do a test run of /etc/dailly.local.restic with:

$ sh /etc/daily.local.restic
open repository
repository f719d564 opened successfully, password is correct
lock repository
load index files
using parent snapshot 05b3f0dd
start scan on [/etc /root /home /usr /bin /sbin /var]
start backup on [/etc /root /home /usr /bin /sbin /var]
scan finished in 2.230s: 35818 files, 2.111 GiB

Files:           0 new,    10 changed, 35808 unmodified
Dirs:            0 new,    12 changed,  1859 unmodified
Data Blobs:      8 new
Tree Blobs:     11 new
Added to the repo: 817.516 KiB

processed 35818 files, 2.111 GiB in 0:05
snapshot 11d4745c saved
comparing snapshot 05b3f0dd to 11d4745c:

M    /root/.local/share/nvim/shada/main.shada
M    /var/cron/log
M    /var/cron/log.0.gz
M    /var/cron/log.1.gz
M    /var/cron/log.2.gz
M    /var/log/daemon
M    /var/log/dhcpd
M    /var/log/messages
M    /var/log/pflog
M    /var/log/rad

Files:           0 new,     0 removed,    10 changed
Dirs:            0 new,     0 removed
Others:          0 new,     0 removed
Data Blobs:      8 new,     8 removed
Tree Blobs:     10 new,    10 removed
  Added:   815.139 KiB
  Removed: 821.155 KiB

Congratulations

This sets up proper daily backups for the server, with blazing speed.

5 minute podman walkthrough

Machine - Gentoo amd64 + OpenRC.

Installing podman

We want to use app-emulation/crun instead of app-emulation/runc - Bug 723080.

$ emerge -av1 app-emulation/crun
$ emerge -av podman

Setup podman

The default configuration file provided (/etc/containers/registries.conf.example) does weird redirections, we don't want that.
Create the config file (/etc/containers/registries.conf) from scratch:

[registries.search]
registries = ['docker.io', 'quay.io', 'registry.fedoraproject.org']

[registries.insecure]
registries = []

#blocked (docker only)
[registries.block]
registries = []

Create the /etc/containers/policy.json file:

$ cp /etc/containers/policy.json{example,}

Optional: login to docker hub and quay.io:

$ podman login docker.io
Username:
Password:
$ podman login quay.io
Username:
Password:

Extra - rootless mode

To enable rootless mode, the tun kernel module must be loaded.
If the tun module is built in to the kernel, then no further steps are necessary.

Look at the Gentoo wiki article on kernel modules for an in-depth explanation.

For loading the tun module, create the file /etc/modules-load.d/networking.conf, with the single line:

tun

Basic terminology

Pods

For practical purposes, pods are going to be a collection of containers.
All containers inside a pod share a network namespace.

If running a pod in macvlan mode, this means all containers in the pod get the same IP address.

Containers

containers are lightweight emulation environments, designed for running programs in isolation from each other.
For our purposes, we will be running small services for our homeserver, with the specific example in this document for the jackett torrent tracker.

Extra - creating a podman network - macvlan

The default config provided only allows for publishing specific ports for containers/pods and provides the default network config podman. We will create a new network which will allow pods/containers to get real IPs on the LAN.

Create the /etc/cni/net.d/88-macvlan.conflist config file:

{
  "cniVersion": "0.4.0",
  "name": "macvlan",
  "plugins": [
    {
      "type": "macvlan",
      "master": "br0",
      "isGateway": true,
      "ipam": {
        "type": "dhcp"
      }
    },
    {
      "type": "portmap",
      "capabilities": {
        "portMappings": true
      }
    },
    {
      "type": "firewall"
    },
    {
      "type": "tuning"
    }
  ]
}

Note: br0 can be substituted for any preconfigured bridge on the host, or can even be an ethernet interface such as enp5s0f0, without losing connectivity for the host machine.

Test the network config

To check that the configuration is sane:

$ podman network ls
NAME           VERSION  PLUGINS
podman         0.4.0    bridge,portmap,firewall,tuning
macvlan        0.4.0    macvlan,portmap,firewall,tuning

Virtual DHCP server for containers

Most containers are not going to have a dhcp client present and thus will not get IP addresses from the dhcp server.

Luckily, net-misc/cni-plugins provides a virtual dhcp server/client which assigns the IP addresses to them. It is pulled in automatically as a dependency.

Start the dhcp server with:

$ rc-update add cni-dhcp default
$ rc-service cni-dhcp start

Dangers of macvlan

Exposing a pod can also expose unwanted and unsafe ports to the network, such as mistakenly exposing a redis port, if redis is employed in a container.
Be careful in choosing the macvlan networking option for a pod/container.

Running a container

There are two ways of running a podman container

  • free floating containers - not inside a pod
  • containers inside a pod

Here, we will only do containers inside a pod.

Create a pod

Create a very simple, empty pod, with the macvlan network.

$ podman pod create --name homeserver --network macvlan

Testing the pod

Add an alpine container to the homeserver pod:

$ podman run --detach --tty --pod homeserver --name homeserver_alpine docker.io/library/alpine:latest top

man podman-run gives a more thorough review of all the command options:

  • --detach: Detached mode: run the container in the background and print the new container ID. The default is false.
  • --tty: Allocate a pseudo-TTY. The default is false.

This creates an alpine linux container inside the homeserver pod and runs the command top in it.

To view the IP address of this pod:

$ podman exec homeserver_alpine ifconfig
eth0      Link encap:Ethernet  HWaddr 82:82:CF:4F:6F:DD  
          inet addr:192.168.2.122  Bcast:192.168.2.255  Mask:255.255.255.0
          inet6 addr: fe80::8082:cfff:fe4f:6fdd/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:14 errors:0 dropped:0 overruns:0 frame:0
          TX packets:13 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:1976 (1.9 KiB)  TX bytes:1552 (1.5 KiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Creating a container inside a pod

We can now run the jackett container in this pod with:

$ podman run --detach --tty --pod homeserver --name=homeserver_jackett ghcr.io/linuxserver/jackett

Test the container

From the above configuration, try to access the jackett webui - http://192.168.2.122:9117

Voila

We are done!
This setup gets rid of pesky port forwarding options and firewall configurations , though at the cost of a potential risk.
Be wise before choosing this configuration.

References

Official documentation - https://docs.podman.io/en/latest/index.html