Continue

This is a continuation of part I. Enjoy!

Init - hostname, user, shell, and tools

First thing, let’s set a new hostname. I’ve used Star Control 2 races as a source for hostnames for many years:

omnios@12-34-56-78:~$ pfexec hostname mycon
omnios@12-34-56-78:~$ echo mycon | pfexec tee /etc/nodename

<reconnect>

omnios@mycon:~$

The second thing to do was configure a new user for me and disable the default user. Though I usually like zsh these days, I’m going to leave it as bash for now.

omnios@mycon:~$ pfexec useradd -m -z -s /bin/bash -d /home/kenichi kenichi
omnios@mycon:~$ pfexec passwd kenichi
omnios@mycon:~$ pfexec usermod -P "Primary Administrator" kenichi
omnios@mycon:~$ pfexec mkdir /home/kenichi/.ssh
omnios@mycon:~$ pfexec vi /home/kenichi/.ssh/authorized_keys

<paste public key>

omnios@mycon:~$ pfexec chown -R kenichi:other /home/kenichi/.ssh
omnios@mycon:~$ pfexec chmod 700 /home/kenichi/.ssh
omnios@mycon:~$ pfexec chmod 600 /home/kenichi/.ssh/authorized_keys

<reconnect>

kenichi@mycon:~$ pfexec passwd -l omnios
kenichi@mycon:~$ pfexec usermod -s /bin/false omnios

Now, we can continue with Getting Started. The OmniOS folks recommend updating:

$ pfexec pkg refresh
$ pkg list -u
$ pfexec pkg update
WARNING: pkg(7) appears to be out of date, and should be updated before
running update.  Please update pkg(7) by executing 'pkg install
pkg:/package/pkg' as a privileged user and then retry the update.

At this point, I had to update pkg itself to continue:

$ pfexec pkg install pkg:/package/pkg
...
$ pfexec pkg update

Now, the docs tell you reboot if this update prcoess tells you to. It told me to. It said there’s a new BE (boot environment) in town, and it will be used on the next reboot. Sounds good, I say, and off we go.

HOLD ON A SEC, this image came with cloud-init services that are configured to run at boot. For a persistent host, I think it is only needed once, so let’s uninstall it and cleanup files it wrote first:

$ pfexec pkg uninstall cloud-init
$ pfexec rm /etc/sudoers.d/90-cloud-init-users
$ pfexec init 6

Once it reboots and we re-login, we can continue with the getting started page, I’ll add the build-essential package. I’ll clearly want tmux, ripgrep, and vim at least though too. Oh, let’s add zsh and git while we’re at it.

$ pfexec pkg install ooce/text/ripgrep \
                     terminal/tmux \
                     editor/vim \
                     shell/zsh \
                     developer/build-essential \
                     developer/versioning/git
...

$ pfexec usermod -s /bin/zsh kenichi

<reconnect>

% tmux

“Crossbow” Networking

Let’s configure the network. I want my zones to live in a NATed subnet for now.

# turn on IPv4 forwarding
% pfexec svcadm enable ipv4-forwarding

# create stub for vnics
% pfexec dladm create-etherstub stub0

# create a gateway vnic linked to the stub
% pfexec dladm create-vnic -l stub0 stub0gw0

# give the gateway vnic an IP
% pfexec ipadm create-addr -T static -a 192.168.42.1/24 stub0gw0/v4

Let’s use ipf to handle firewalling and NAT. We’ll start with very basic rules. For a more complete overview and ruleset, see IPFilter. Write these files:

/etc/ipf/ipf.conf
# by default, block everything
block in all
block out all

# loopback
pass in quick on lo0 all
pass out quick on lo0 all

# ena0 egress
pass out quick on ena0 all keep state

# ena0 ingress
pass in quick on ena0 proto tcp from any to any port = 22 flags S keep state

# zones
pass in quick on stub0gw0 from 192.168.42.0/24 to any keep state
pass out quick on stub0gw0 from any to 192.168.42.0/24 keep state
/etc/ipf/ipnat.conf
# NAT all traffic from zone network out through ena0
map ena0 192.168.42.0/24 -> 0.0.0.0/32 portmap tcp/udp auto
map ena0 192.168.42.0/24 -> 0.0.0.0/32

Then, start it up and load our rules:

% svcdam enable svc:/network/ipfilter
% ipf -Fa -f /etc/ipf/ipf.conf
% ipnat -FC -f /etc/ipf/ipnat.conf

Great, now we have a private subnet to boot zones in that can reach the internet via NAT. We’ve also allowed SSH from anywhere; you should probably rate-limit this or lock it down with a security group.

Let’s make sure the configuration works. First, we’ll configure a sparse zone:

% pfexec zfs create /rpool/zones

% pfexec zonecfg -z sparsetest
sparsetest: No such zone configured
Use 'create' to begin configuring a new zone.
zonecfg:signoz-collector> create
zonecfg:signoz-collector> set zonepath=/rpool/zones/sparsetest
zonecfg:signoz-collector> set autoboot=false
zonecfg:signoz-collector> set ip-type=exclusive
zonecfg:signoz-collector> set brand=sparse
zonecfg:signoz-collector> add net
zonecfg:signoz-collector:net> set allowed-address=192.168.42.2/24
zonecfg:signoz-collector:net> set defrouter=192.168.42.1
zonecfg:signoz-collector:net> set physical=stub0sparsetest0
zonecfg:signoz-collector:net> end
zonecfg:signoz-collector> add attr
zonecfg:signoz-collector:attr> set name=resolvers
zonecfg:signoz-collector:attr> set type=string
zonecfg:signoz-collector:attr> set value=10.0.0.1
zonecfg:signoz-collector:attr> end
zonecfg:signoz-collector> verify
zonecfg:signoz-collector> commit
zonecfg:signoz-collector> exit

zoneadm will take care of a number of things for us:

  1. Creating the ZFS dataset at zonepath
  2. Creating the VNIC stub0sparsetest0 with 192.168.42.2
  3. Creating /etc/resolv.conf in the zone with 10.0.0.1 (this IP is generic, adjust for your own setup)

Let’s see it happen!

% pfexec zoneadm -z sparsetest install
...

% pfexec zoneadm -z sparsetest boot

% pfexec zlogin sparsetest
[Connected to zone 'sparsetest' pts/6]
OmniOS r151054  omnios-r151054-f66c95f374       September 2025

root@sparsetest:~# ping google.com
google.com is alive

🎉 Success! We have configured our host, set up some basic Crossbow networking, and fired up a sparse-brand zone. We’ll continue with running an LX zone based on a Docker image in part III.