2. Installing directly to disk

Before delving into the inners of the installation, we need to retrieve the disk geometry values, which, as we will see, will come in handy more than once. To get this information, insert the Compact Flash card into its socket, attach to the device's serial console with a null-modem cable, connect with cu(1) and power the system up. You should get something like:

# cu -s 19200 -l cua00

comBIOS ver. 1.26a  20040819  Copyright (C) 2000-2004 Soekris Engineering.

net45xx

0064 Mbyte Memory                        CPU 80486 133 Mhz 

Pri Mas  SanDisk SDCFB-64                LBA 490-8-32  62 Mbyte
[...]

The numbers 490, 8 and 32 are, respectively, the number of cylinders, heads (i.e. tracks per cylinder) and sectors (per track) of the disk.

Ok, now let the fun begin! We will create a bootable filesystem on the flash card and copy the files we need from the OS. To make fewer write operations on the memory card, the best thing is to create a disk-image file of the size of the CF card (see vnd(4) for details) and eventually copy it to the device. For instance, to create a 64MB virtual disk image, type:

# dd if=/dev/zero of=net4521.img bs=512 count=125440
125440+0 records in
125440+0 records out
64225280 bytes transferred in 1.399 secs (45875823 bytes/sec)
# vnconfig -c svnd0 net4521.img

The bs parameter sets the block (sector) size (usually 512 bytes), and count the number of sectors, obtained by multiplying the disk geometry values (32 * 8 * 490). Note: if you want to write directly to the disk, without bothering with the virtual disk image, simply replace svnd0 with the appropriate disk drive (e.g. sd0) in the subsequent examples.

After creating the virtual disk, we need to disklabel(8) it, build the filesystem and make it bootable; but to fully understand these steps, we must first discuss how OpenBSD boots on the i386 architecture. So let's take a look, in parallel, at the boot process and how it reflects upon our installation procedure (for more information, please refer to [FAQ14]).

2.1 Master Boot Record

The Master Boot Record is the first physical sector (512 bytes) on the disk; it is loaded by the BIOS after the POST and it contains the primary partition table (Master Partition Table) and a small program (Master Boot Code) to load the Partition Boot Record (see below).

OpenBSD provides a "MBR template file" (/usr/mdec/mbr) which we can install with fdisk(8):

# fdisk -c 490 -h 8 -s 32 -iyf /usr/mdec/mbr svnd0
Writing MBR at offset 0.
#

We need to specify the disk geometry (we have seen before how to retrieve these data) because we're not installing directly to the disk now, but to a virtual disk image. Note: if the OpenBSD release you're installing from is not the same as the release you're installing, you can extract the mbr file from the baseXX.tgz file set.

2.2 First and second stage boot loaders

Next, the OpenBSD boot process goes through two stages:

Before installing the boot loaders, we need to create the disklabel(5), which contains detailed information about disk geometry and partitions and acts as an interface between the disk and the disk drivers contained within the kernel. The disklabel(8) utility allows you to write the label on the disk (once again, disk geometry information will come in handy):

# disklabel -E svnd0
Label editor (enter '?' for help at any prompt)
> e
Changing device parameters for /dev/rsvnd0c:
disk type: [vnd] ESDI
label name: [fictitious] net4521
sectors/track: [100] 32
tracks/cylinder: [1] 8
sectors/cylinder: [100] 256
number of cylinders: [1254] 490
total sectors: [125440] <enter>
> a a
offset: [0] 63
size: [125377] <enter>
FS type: [4.2BSD] <enter>
> q
Write new label?: [y] y
#

We have created only a single "/" partition: swapping on the compact flash is strongly discouraged. Now we can build the filesystem:

# newfs -S 512 /dev/rsvnd0a
newfs: reduced number of fragments per cylinder group from 7832 to 7792 to enlarge last cylinder group
/dev/rsvnd0a: 61.2MB in 125376 sectors of 512 bytes
5 cylinder groups of 15.22MB, 974 blocks, 2048 inodes each
super-block backups (for fsck -b #) at:
 32, 31200, 62368, 93536, 124704,
#

mount it and install the two boot loaders with the installboot(8) command:

# mkdir /mnt/net4521
# mount /dev/svnd0a /mnt/net4521
# cp /usr/mdec/boot /mnt/net4521/
# /usr/mdec/installboot /mnt/net4521/boot /usr/mdec/biosboot svnd0

We can now set up some boot parameters in the /etc/boot.conf(5) configuration file. We will use it to set up the serial console, which has a default baud rate of 19200 (or 38400 for ALIX boards):

/mnt/net4521/etc/boot.conf
set tty com0
stty com0 19200

2.3 Building a custom kernel

Now that the disk is ready, we only have to populate it. Let's start with the kernel, for which we have two options: if the CF card is not too small, the easy and smooth (and recommended) way is copying the standard bsd kernel to it:

# cp /bsd /mnt/net4521/

Or else, if you need the kernel to be smaller and faster at boot time, you can build a custom kernel with only the bare minimum features. The following is a sample configuration file suitable for the latter case:

/usr/src/sys/arch/i386/conf/NET4521
# OpenBSD config file for Soekris net4521 embedded system

machine		i386			# architecture, used by config; REQUIRED
option		I486_CPU

# Operation Related Options
option		DUMMY_NOPS		# speed hack; recommended

# Debugging Options
option		DDB

# Filesystem Options
option		FFS
option		MFS
option		NFSCLIENT
option		FDESC
option          FIFO

# Miscellaneous Options
option		PCIVERBOSE
option		CRYPTO
option		TIMER_FREQ=1189161
option		PCCOMCONSOLE
option		CONSPEED=19200

# Networking Options
option		INET
option		INET6
option		TCP_SACK
option		TCP_FACK
option          TCP_SIGNATURE
option		IPSEC
option		KEY
option		ALTQ
option		ALTQ_NOPCC

maxusers	5			# estimated number of users
config		bsd root on wd0a

mainbus0 	at root

cpu0		at mainbus?
bios0		at mainbus0
pcibios0 	at bios0 flags 0x0000	# use 0x30 for a total verbose

isa0		at mainbus0
pci*		at mainbus0

# power management and other environmental stuff
elansc*		at pci?			# AMD Elan SC520 System Controller
gpio*		at elansc?

# CardBus bus support
cardbus*	at cardslot?
pcmcia*		at cardslot?
cbb*		at pci?
cardslot*	at cbb?

npx0		at isa? port 0xf0 irq 13	# math coprocessor
isadma0		at isa?

com0		at isa? port 0x3f8 irq 4        # standard PC serial ports
com1		at isa? port 0x2f8 irq 3
com2		at isa? port 0x3e8 irq 5

# IDE
wdc0		at isa? port 0x1f0 irq 14 flags 0x00	# WD100x compatible hard disk controller driver
wd*		at wdc? flags 0x0000	# WD100x compatible hard disk driver

# Networking devices
sis*		at pci?			# SiS 900/7016 ethernet Fast Ethernet driver
nsphyter*	at mii? phy ?		# NS and compatible PHYs

# Wireless network cards
wi*		at pcmcia?		# PRISM 2-3 wireless network driver

# Pseudo-devices
pseudo-device	mtrr		1	# driver for CPU memory range attributes
pseudo-device	nvram		1	# driver for reading PC NVRAM contents
pseudo-device	bio		1	# ioctl tunnel pseudo-device
pseudo-device	hotplug		1	# devices hot plugging

pseudo-device	ksyms		1	# kernel symbol table device
pseudo-device	systrace	1	# enforce and generate policies for system calls

pseudo-device	pf			# Packet filter
pseudo-device	pflog			# Packet filter logging interface
pseudo-device	pfsync			# Packet filter state table logging interface
pseudo-device	loop		2	# Loopback
pseudo-device	bpfilter 	16	# Berkeley Packet Filter
pseudo-device	tun		2	# Network tunnel pseudo-device
pseudo-device	enc		1	# IPSEC encapsulating Interface
pseudo-device	bridge		2	# Ethernet bridge interface
pseudo-device	vlan		32	# IEEE 802.1Q encapsulation/decapsulation pseudo-device
pseudo-device	gre		4	# GRE encapsulating network device
pseudo-device	pty		32	# Pseudo-terminals
pseudo-device	gif		4	# Generic tunnel interface

So let's build the kernel and install it:

# cd /usr/src/sys/arch/i386/conf
# config NET4521
Don't forget to run "make depend"
# cd ../compile/NET4521
# make clean && make depend && make
[...]
# cp bsd /mnt/net4521/

2.4 Populating the filesystem

Next we will create the necessary configuration files in /etc (well, for the moment /mnt/net4521/etc/). We will only list the main ones here: a comprehensive list would be too dependent on the purpose of the device.

/etc/fstab(5)
this file contains information about the filesystems.
/mnt/net4521/etc/fstab
/dev/wd0a	/	ffs	ro				1 1
swap		/tmp	mfs	rw,nosuid,-P=/tmplate,-s=16384	0 0

As stated before, we map the /tmp filesystem to memory. /var and /root, which must be read-write, will be symbolic links to /tmp/var and /tmp/root respectively. We will also create a /tmplate directory containing the directory tree which mount_mfs(8) will use to populate /tmp after its creation (we will put pseudo-devices and files required by syslogd(8) into this directory later);

# mkdir /mnt/net4521/tmp{late,}
# ln -s /tmp/{var,root} /mnt/net4521/
# mkdir -p /mnt/net4521/tmplate/var/cron/{tabs,atjobs}
# chmod 555 /mnt/net4521/tmplate/var/cron
# chmod 1770 /mnt/net4521/tmplate/var/cron/atjobs
# chmod 1730 /mnt/net4521/tmplate/var/cron/cron
# mkdir -p /mnt/net4521/tmplate/root
# chmod 700 /mnt/net4521/tmplate/root
network configuration files
/etc/hosts(5) (host name database), /etc/hostname.if(5) (interface-specific configuration files), /etc/myname(5) (default hostname), /etc/mygate(5) (default gateway), /etc/resolv.conf(5) (resolver configuration file);
users configuration files
/etc/group(5) (group permissions file) and /etc/master.passwd(5) (password file); the other files (/etc/passwd(5), /etc/pwd.db, /etc/spwd.db) will be generated by the pwd_mkdb(8) command:
# echo "root:$(encrypt -b 8 mypasswd):0:0:daemon:0:0:Charlie &,,,:/root:/bin/ksh" >> /mnt/net4521/etc/master.passwd
# echo "wheel:*:0:root" >> /mnt/net4521/etc/group
# pwd_mkdb -d /mnt/net4521/etc /mnt/net4521/etc/master.passwd

Feel free to add all the system and administrative users and groups you will need. If you want to use sudo(8), which is usually a good idea, you need to create the sudoers(5) file (using the "visudo -f /mnt/net4521/etc/sudoers" command);

pf(4) configuration files
/etc/pf.conf(5) (configuration and rules) e /etc/pf.os(5) (OS fingerprints);
ssh(1) configuration files
/etc/ssh/ssh_config (SSH client configuration file), /etc/ssh/sshd_config (SSH daemon configuration file), /etc/moduli(5) (system Diffie-Hellman moduli file). We can also generate the host private keys right now:
# ssh-keygen -t rsa -f /mnt/net4521/etc/ssh/ssh_host_rsa_key -N ""
# ssh-keygen -t rsa1 -f /mnt/net4521/etc/ssh/ssh_host_key -N ""
# ssh-keygen -t dsa -f /mnt/net4521/etc/ssh/ssh_host_dsa_key -N ""
/etc/syslog.conf(5)
containing syslogd(8) configuration. All log files have to be created (a touch(1) will suffice), otherwise syslogd(8) will complain on boot:
# mkdir -p /mnt/net4521/tmplate/var/{log,run/dev}
# touch /mnt/net4521/tmplate/var/log/{authlog,daemon,messages,secure}
# touch /mnt/net4521/tmplate/var/run/utmp
# chmod 640 /mnt/net4521/tmplate/var/log/{authlog,daemon}
# chmod 600 /mnt/net4521/tmplate/var/log/secure
# chmod 664 /mnt/net4521/tmplate/var/run/utmp

You may schedule newsyslog(8) to periodically archive log files:

# echo "0 * * * *  /usr/bin/newsyslog" > /mnt/net4521/tmplate/var/cron/tabs/root
# chmod 600 /mnt/net4521/tmplate/var/cron/tabs/root

Anyway, since /var will reside on volatile memory, it is recommended to forward log messages to a remote log host;

/etc/ttys(5)
the terminal initialization file, modified according to our configuration:
/mnt/net4521/etc/ttys
console	"/usr/libexec/getty Pc"		vt220	off secure
ttyC0	"/usr/libexec/getty Pc"		vt220	off secure
ttyC1	"/usr/libexec/getty Pc"		vt220	off secure
ttyC2	"/usr/libexec/getty Pc"		vt220	off secure
ttyC3	"/usr/libexec/getty Pc"		vt220	off secure
tty00	"/usr/libexec/getty std.19200"	vt100	on  secure
tty01	"/usr/libexec/getty std.9600"	unknown off
tty02	"/usr/libexec/getty std.9600"	unknown off
tty03	"/usr/libexec/getty std.9600"	unknown off
ttyp0	none				network
ttyp1	none				network
ttyp2	none				network
[...]
/etc/sysctl.conf(5)
containing sysctl(8) variables to set at system startup; e.g.:
/mnt/net4521/etc/sysctl.conf
net.inet.ip.forwarding=1
[...]

Next, we need to copy the startup scripts (rc(8), rc.local(8), rc.securelevel(8), rc.conf(8), rc.conf.local(8), rc.shutdown(8), netstart(8)) and create the device files:

# mkdir /mnt/net4521/dev/
# cp /dev/MAKEDEV /mnt/net4521/dev/
# cd /mnt/net4521/dev/
# ./MAKEDEV tun0 tun1 tun2 tun3 bpf0 bpf1 bpf2 bpf3 bpf4 bpf5 bpf6 bpf7 bpf8   \
> bpf9 fd1 fd1B fd1C fd1D fd1E fd1F fd1G fd1H fd0 fd0B fd0C fd0D fd0E fd0F     \
> fd0G fd0H random crypto pf pctr systrace sd0 sd1 sd2 sd3 sd4 wd0 wd1 wd2 wd3 \
> fd tty00 tty01 tty02 tty03 ttyc0 ttyc1 ttyc2 ttyc3 apm std
#

Note: rc(8) clears the /tmp directory on boot, thus removing the contents of the /var and /root directories; therefore, I would recommend that you delete the following lines from /mnt/net4521/etc/rc:

/mnt/net4521/etc/rc
(cd /tmp && rm -rf [a-km-pr-zA-Z]*)
(cd /tmp &&
    find . ! -name . ! -name lost+found ! -name quota.user \
        ! -name quota.group -execdir rm -rf -- {} \; -type d -prune)

/dev/log, used by syslogd(8), must be writable: therefore, we turn it into a symlink to /var/run/dev/log. The same applies to pseudo terminals, which must be able to change owner and permissions:

# ln -s /var/run/dev/log /mnt/net4521/dev/log
# cd /mnt/net4521/tmplate/var/run/dev/
# /dev/MAKEDEV pty
# for dev in [tp]typ?; do
>     ln -s /var/run/dev/$dev /mnt/net4521/dev/$dev
> done
#

Finally, we can install binaries and libraries. The simplest way is copying them from the system currently in use, or you may extract them from the installation file set (baseXX.tgz). To save some time, you can create a file with the list of the binaries to copy (a good starting point is flashsmall.txt from flashdist):

# tar -I bin_list.txt -cf - | tar -C /mnt/net4521/ -xpf -
tar: Removing leading / from absolute path names in the archive
# while read file; do
>     ldd $file 2>/dev/null | egrep 'rlib|rtld' | awk '{ print $7 }' 
> done < bin_list.txt | sort -u | xargs tar -cvf - | tar -C /mnt/net4521/ -xpf -
tar: Removing leading / from absolute path names in the archive
[...]
#

If you wish to further decrease the binaries disk space, you can take a look at crunchgen(8), which builds them all in a single binary file that modifies its behaviour according to argv[0], or remove the debugging symbols from the shared libraries using the strip(1) command:

# strip -S /mnt/net4521/usr/lib/lib*

Now we only have to transfer the virtual filesystem we have created to the memory card:

# umount /mnt/net4521
# vnconfig -u svnd0
# dd if=net4521.img of=/dev/sd0c bs=512
125440+0 records in
125440+0 records out
64225280 bytes transferred in 383.307 secs (167556 bytes/sec)
#

Plug the compact flash into the device, power it up and ...uncork the champagne!