<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://tyk.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Tykling</id>
	<title>TykWiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://tyk.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Tykling"/>
	<link rel="alternate" type="text/html" href="https://tyk.wiki/Special:Contributions/Tykling"/>
	<updated>2026-05-10T13:05:44Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.1</generator>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=718</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=718"/>
		<updated>2022-01-24T18:18:19Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Fetching sources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
First I get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@jail1 ~]$ sudo git clone -b stable/13 https://git.freebsd.org/src.git /usr/src&lt;br /&gt;
Password:&lt;br /&gt;
Cloning into &#039;/usr/src&#039;...&lt;br /&gt;
remote: Enumerating objects: 4060913, done.&lt;br /&gt;
remote: Counting objects: 100% (379329/379329), done.&lt;br /&gt;
remote: Compressing objects: 100% (27474/27474), done.&lt;br /&gt;
remote: Total 4060913 (delta 373583), reused 351855 (delta 351855), pack-reused 3681584&lt;br /&gt;
Receiving objects: 100% (4060913/4060913), 1.38 GiB | 5.94 MiB/s, done.&lt;br /&gt;
Resolving deltas: 100% (3217803/3217803), done.&lt;br /&gt;
Updating files: 100% (86931/86931), done.&lt;br /&gt;
[tykling@jail1 ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent &amp;lt;code&amp;gt;git pull&amp;lt;/code&amp;gt; runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use svn or svnlite here but since the migration to Git I switched to using the regular git client&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo -i bash&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot into the newly built kernel and run mergemaster, installworld, and mergemaster again; and finally delete-old and delete-old-libs:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo -i bash&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# mergemaster -pFUi &amp;amp;&amp;amp; make installworld &amp;amp;&amp;amp; mergemaster -FUi &amp;amp;&amp;amp; make -DBATCH_DELETE_OLD_FILES delete-old &amp;amp;&amp;amp; make -DBATCH_DELETE_OLD_FILES delete-old-libs&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PAY ATTENTION DURING MERGEMASTER! DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=717</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=717"/>
		<updated>2022-01-24T18:15:30Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Fetching sources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
First I get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo git clone -b stable/13 https://git.freebsd.org/src.git /usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent &amp;lt;code&amp;gt;git pull&amp;lt;/code&amp;gt; runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use svn or svnlite here but since the migration to Git I switched to using the regular git client&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo -i bash&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot into the newly built kernel and run mergemaster, installworld, and mergemaster again; and finally delete-old and delete-old-libs:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo -i bash&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# mergemaster -pFUi &amp;amp;&amp;amp; make installworld &amp;amp;&amp;amp; mergemaster -FUi &amp;amp;&amp;amp; make -DBATCH_DELETE_OLD_FILES delete-old &amp;amp;&amp;amp; make -DBATCH_DELETE_OLD_FILES delete-old-libs&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PAY ATTENTION DURING MERGEMASTER! DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=716</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=716"/>
		<updated>2020-10-28T16:35:13Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
First I get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svnlite checkout https://svn.freebsd.org/base/stable/11/ /usr/src/&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    kerberos5/include/version.h&lt;br /&gt;
A    COPYRIGHT&lt;br /&gt;
A    LOCKS&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile.inc&lt;br /&gt;
A    kerberos5/include/krb5-types.h&lt;br /&gt;
A    .arcconfig&lt;br /&gt;
A    kerberos5/usr.sbin/iprop-log/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/kstash/Makefile&lt;br /&gt;
A    kerberos5/include/crypto-headers.h&lt;br /&gt;
A    MAINTAINERS&lt;br /&gt;
A    kerberos5/include/config.h&lt;br /&gt;
A    kerberos5/README&lt;br /&gt;
A    .arclint&lt;br /&gt;
 U   .&lt;br /&gt;
Checked out revision 319138.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent &amp;lt;code&amp;gt;svnlite update&amp;lt;/code&amp;gt; runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use the full svn client here but svnlite(1) which comes with base does a great job of fetching the sources these days.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo -i bash&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot into the newly built kernel and run mergemaster, installworld, and mergemaster again; and finally delete-old and delete-old-libs:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo -i bash&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# mergemaster -pFUi &amp;amp;&amp;amp; make installworld &amp;amp;&amp;amp; mergemaster -FUi &amp;amp;&amp;amp; make -DBATCH_DELETE_OLD_FILES delete-old &amp;amp;&amp;amp; make -DBATCH_DELETE_OLD_FILES delete-old-libs&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PAY ATTENTION DURING MERGEMASTER! DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=715</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=715"/>
		<updated>2020-10-23T13:46:10Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
First I get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svnlite checkout https://svn.freebsd.org/base/stable/11/ /usr/src/&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    kerberos5/include/version.h&lt;br /&gt;
A    COPYRIGHT&lt;br /&gt;
A    LOCKS&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile.inc&lt;br /&gt;
A    kerberos5/include/krb5-types.h&lt;br /&gt;
A    .arcconfig&lt;br /&gt;
A    kerberos5/usr.sbin/iprop-log/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/kstash/Makefile&lt;br /&gt;
A    kerberos5/include/crypto-headers.h&lt;br /&gt;
A    MAINTAINERS&lt;br /&gt;
A    kerberos5/include/config.h&lt;br /&gt;
A    kerberos5/README&lt;br /&gt;
A    .arclint&lt;br /&gt;
 U   .&lt;br /&gt;
Checked out revision 319138.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent &amp;lt;code&amp;gt;svnlite update&amp;lt;/code&amp;gt; runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use the full svn client here but svnlite(1) which comes with base does a great job of fetching the sources these days.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo -i bash&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot into the newly built kernel and run mergemaster, installworld, and mergemaster again; and finally delete-old and delete-old-libs:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi &amp;amp;&amp;amp; make -DBATCH_DELETE_OLD_FILES delete-old &amp;amp;&amp;amp; make -DBATCH_DELETE_OLD_FILES delete-old-libs&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;PAY ATTENTION DURING MERGEMASTER! DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Main_Page&amp;diff=714</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Main_Page&amp;diff=714"/>
		<updated>2019-05-08T19:57:23Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;See [[TykWiki:About]] for more about what this wiki is and isn&#039;t, or go straight to the categories below. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;border: 1px solid #ff0000;&amp;quot;&amp;gt;&amp;lt;p&amp;gt;&amp;lt;h3&amp;gt;This wiki was moved to a faster server on May 8th 2019. Please report any problems!&amp;lt;/h3&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also [[special:Search|search]] for the information you need.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[User:Tykling|Tykling]] 12:50, 23 February 2009 (UTC)&lt;br /&gt;
&lt;br /&gt;
== Available categories ==&lt;br /&gt;
* [[:Category:Apache|Apache]] - Articles about the Apache http server.&lt;br /&gt;
* [[:Category:Eggdrop|Eggdrop]] - Articles about running the Eggdrop IRC bot from ports on FreeBSD.&lt;br /&gt;
* [[:Category:GEOM Related|GEOM Related]] - Articles about gjournal, gmirror and friends.&lt;br /&gt;
* [[:Category:miscellaneous|Miscellaneous]] - Articles that do not fit into any other categories.&lt;br /&gt;
* [[:Category:Nagios|Nagios]] - Articles about the Nagios monitoring system.&lt;br /&gt;
* [[:Category:Ports Management|Ports Management]] - Articles about FreeBSD ports management.&lt;br /&gt;
* [[:Category:postfix|Postfix]] - Articles about the Postfix MTA.&lt;br /&gt;
* [[:Category:postgres|PostgreSQL]] - Articles about the PostgreSQL database server.&lt;br /&gt;
* [[:Category:sendmail|Sendmail]] - Articles about the default FreeBSD MTA Sendmail.&lt;br /&gt;
* [[:Category:Tcpdump|Tcpdump]] - Articles about the tcpdump network packet sniffer.&lt;br /&gt;
* [[:Category:Varnish|Varnish]] - Articles about the reverse HTTP proxy Varnish.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ssh_keys&amp;diff=713</id>
		<title>Ssh keys</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ssh_keys&amp;diff=713"/>
		<updated>2017-07-18T20:46:07Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;pre&amp;gt;&lt;br /&gt;
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPcy6uofmC1uJ9xmgTVX44BznS7fVb20pw4FE1jAbRXZ tykling@anywhere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=712</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=712"/>
		<updated>2017-05-29T14:17:28Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Building world and kernel */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
First I get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svnlite checkout https://svn.freebsd.org/base/stable/11/ /usr/src/&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    kerberos5/include/version.h&lt;br /&gt;
A    COPYRIGHT&lt;br /&gt;
A    LOCKS&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile.inc&lt;br /&gt;
A    kerberos5/include/krb5-types.h&lt;br /&gt;
A    .arcconfig&lt;br /&gt;
A    kerberos5/usr.sbin/iprop-log/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/kstash/Makefile&lt;br /&gt;
A    kerberos5/include/crypto-headers.h&lt;br /&gt;
A    MAINTAINERS&lt;br /&gt;
A    kerberos5/include/config.h&lt;br /&gt;
A    kerberos5/README&lt;br /&gt;
A    .arclint&lt;br /&gt;
 U   .&lt;br /&gt;
Checked out revision 319138.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent &amp;lt;code&amp;gt;svnlite update&amp;lt;/code&amp;gt; runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use the full svn client here but svnlite(1) which comes with base does a great job of fetching the sources these days.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo -i bash&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=711</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=711"/>
		<updated>2017-05-29T14:15:57Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Fetching sources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
First I get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svnlite checkout https://svn.freebsd.org/base/stable/11/ /usr/src/&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    kerberos5/include/version.h&lt;br /&gt;
A    COPYRIGHT&lt;br /&gt;
A    LOCKS&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile.inc&lt;br /&gt;
A    kerberos5/include/krb5-types.h&lt;br /&gt;
A    .arcconfig&lt;br /&gt;
A    kerberos5/usr.sbin/iprop-log/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/kstash/Makefile&lt;br /&gt;
A    kerberos5/include/crypto-headers.h&lt;br /&gt;
A    MAINTAINERS&lt;br /&gt;
A    kerberos5/include/config.h&lt;br /&gt;
A    kerberos5/README&lt;br /&gt;
A    .arclint&lt;br /&gt;
 U   .&lt;br /&gt;
Checked out revision 319138.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent &amp;lt;code&amp;gt;svnlite update&amp;lt;/code&amp;gt; runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use the full svn client here but svnlite(1) which comes with base does a great job of fetching the sources these days.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=710</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=710"/>
		<updated>2017-05-29T14:15:39Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Fetching sources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
First I get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svnlite checkout https://svn.freebsd.org/base/stable/11/ /usr/src/&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    kerberos5/include/version.h&lt;br /&gt;
A    COPYRIGHT&lt;br /&gt;
A    LOCKS&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile.inc&lt;br /&gt;
A    kerberos5/include/krb5-types.h&lt;br /&gt;
A    .arcconfig&lt;br /&gt;
A    kerberos5/usr.sbin/iprop-log/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/Makefile&lt;br /&gt;
A    kerberos5/usr.sbin/ktutil/Makefile.depend&lt;br /&gt;
A    kerberos5/usr.sbin/kstash/Makefile&lt;br /&gt;
A    kerberos5/include/crypto-headers.h&lt;br /&gt;
A    MAINTAINERS&lt;br /&gt;
A    kerberos5/include/config.h&lt;br /&gt;
A    kerberos5/README&lt;br /&gt;
A    .arclint&lt;br /&gt;
 U   .&lt;br /&gt;
Checked out revision 319138.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent &amp;lt;code&amp;gt;svnlite update&amp;lt;/code&amp;gt; runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use the full svn client here but svnlite(1) which comes with base does a great job of fetching the sources these days.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=709</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=709"/>
		<updated>2017-05-29T14:12:13Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Fetching sources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
First I get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svnlite checkout https://svn.freebsd.org/base/stable/11/ /usr/src/&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent &amp;lt;code&amp;gt;svnlite update&amp;lt;/code&amp;gt; runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use the full svn client here but svnlite(1) which comes with base does a great job of fetching the sources these days.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=708</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=708"/>
		<updated>2017-05-29T14:11:49Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Upgrade OS (buildworld) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svnlite checkout https://svn.freebsd.org/base/stable/11/ /usr/src/&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent &amp;lt;code&amp;gt;svnlite update&amp;lt;/code&amp;gt; runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use the full svn client here but svnlite(1) which comes with base does a great job of fetching the sources these days.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=707</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=707"/>
		<updated>2017-05-29T14:11:09Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Fetching sources */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svnlite checkout https://svn.freebsd.org/base/stable/11/ /usr/src/&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; I used to use the full svn client here but svnlite(1) which comes with base does a great job of fetching the sources these days.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=706</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=706"/>
		<updated>2016-11-02T11:16:39Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Periodic snapshots using sysutils/zfs-periodic */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip=&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
daily_scrub_zfs_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_scrub_zfs_default_threshold=30&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=705</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=705"/>
		<updated>2016-11-02T11:12:41Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Periodic snapshots using sysutils/zfs-periodic */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
daily_zfs_snapshot_skip&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
weekly_zfs_snapshot_skip&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
monthly_zfs_snapshot_skip&amp;quot;gelipool/backups&amp;quot;&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=704</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=704"/>
		<updated>2016-10-28T11:41:56Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Running mergemaster in the jails */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
        MM_RC=1&lt;br /&gt;
        mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailname in $(ls -1 /usr/jails/ | grep -Ev &amp;quot;(^basejail$|^newjail$|^flavours$)&amp;quot;); do&lt;br /&gt;
        jailroot=&amp;quot;/usr/jails/${jailname}&amp;quot;&lt;br /&gt;
        echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
        ### check if jailroot exists&lt;br /&gt;
        if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
                ### create .mergemasterrc&lt;br /&gt;
                cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
                ### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
                if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
                        rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
                fi&lt;br /&gt;
&lt;br /&gt;
                ### create backup of /etc as /etc.bak&lt;br /&gt;
                cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
&lt;br /&gt;
                ### check if mtree from last mergemaster run exists&lt;br /&gt;
                if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
                        ### delete /etc/rc.d/*&lt;br /&gt;
                        rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
                fi&lt;br /&gt;
                ### run mergemaster for this jail&lt;br /&gt;
                mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
        else&lt;br /&gt;
                echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
        fi&lt;br /&gt;
        sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
        mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
        rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=703</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=703"/>
		<updated>2016-09-29T16:41:50Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Create kernel config */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
I used to create a kernel config to get &amp;lt;code&amp;gt;RACCT&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RCTL&amp;lt;/code&amp;gt; but these days both are included in GENERIC, so no need for that anymore. Yay.&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=702</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=702"/>
		<updated>2016-09-20T09:42:10Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Create the target filesystems in the backup jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=701</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=701"/>
		<updated>2016-09-20T09:41:31Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        ### check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=700</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=700"/>
		<updated>2016-09-20T09:39:56Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                #dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                #lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=699</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=699"/>
		<updated>2016-09-20T09:39:11Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Add the periodic script */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#set -x&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                #dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        echo $lastgoodsnap&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        echo $lastgoodsnap&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                #lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$pool/$snapshot to ${targethost}@${targetfs}/${pool} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=698</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=698"/>
		<updated>2016-09-15T06:55:46Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Add the periodic script */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if mbuffer is installed&lt;br /&gt;
which mbuffer &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;mbuffer must be installed&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                #dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=697</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=697"/>
		<updated>2016-09-15T06:53:46Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Add the periodic script */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
### check if the destination fs exists&lt;br /&gt;
ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list ${targetfs} &amp;gt; /dev/null 2&amp;gt;&amp;amp;1&lt;br /&gt;
if [ $? -ne 0 ]; then&lt;br /&gt;
        echo &amp;quot;Creating destination fs on target server&amp;quot;&lt;br /&gt;
        echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
        ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh create ${targetfs}&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool[\/\@]&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                #dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | mbuffer | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u $targetfs/$dataset&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=696</id>
		<title>Poudriere in a jail</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=696"/>
		<updated>2016-08-03T13:49:27Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* Create a normal ezjail&lt;br /&gt;
&lt;br /&gt;
* Create a ZFS dataset for the jail&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o jailed=on tyktank/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Change settings in ezjail config&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_poudriere_tyknet_dk_zfs_datasets=&amp;quot;tyktank/poudriere&amp;quot;&lt;br /&gt;
export jail_poudriere_tyknet_dk_parameters=&amp;quot;children.max=100 allow.mount allow.mount.tmpfs allow.mount.devfs allow.mount.procfs allow.mount.zfs allow.mount.nullfs allow.raw_sockets allow.socket_af allow.sysvipc allow.chflags enforce_statfs=1 ip6=inherit ip4=inherit&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add needed kld modules to rc.conf (and load them manually)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
kld_list=&amp;quot;zfs aesni geom_mirror tmpfs linux linprocfs nullfs procfs fdescfs&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Install needed ports&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo portmaster www/nginx ports-mgmt/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create key and cert for signing packages&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo mkdir -p /usr/local/etc/ssl/{keys,certs}&lt;br /&gt;
$ sudo chmod 0600 /usr/local/etc/ssl/keys&lt;br /&gt;
$ sudo openssl genrsa -out /usr/local/etc/ssl/keys/poudriere.key 4096&lt;br /&gt;
$ sudo openssl rsa -in /usr/local/etc/ssl/keys/poudriere.key -pubout -out /usr/local/etc/ssl/certs/poudriere.crt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create &amp;lt;code&amp;gt;/usr/local/etc/poudriere.conf&amp;lt;/code&amp;gt; with the following contents:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ZPOOL=tyktank&lt;br /&gt;
ZROOTFS=/poudriere&lt;br /&gt;
FREEBSD_HOST=ftp://ftp.dk.freebsd.org&lt;br /&gt;
RESOLV_CONF=/etc/resolv.conf&lt;br /&gt;
BASEFS=/usr/local/poudriere&lt;br /&gt;
USE_PORTLINT=no&lt;br /&gt;
USE_TMPFS=yes&lt;br /&gt;
DISTFILES_CACHE=/usr/ports/distfiles&lt;br /&gt;
PKG_REPO_SIGNING_KEY=/usr/local/etc/pki/poudriere/poudriere.key&lt;br /&gt;
NOLINUX=yes&lt;br /&gt;
BUILDER_HOSTNAME=poudriere.tyknet.dk&lt;br /&gt;
&lt;br /&gt;
# https://gist.github.com/gynter/86ed7a6cae20927d6ef0&lt;br /&gt;
USE_MASTERMNT_HASH=yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* The last line is not a default setting but one that is neccesary due to path length restrictions combined with jails. To make it work apply the patch in the link above.&lt;br /&gt;
&lt;br /&gt;
* Create one or more jails for the versions you need to build for&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo poudriere jail -c -j freebsd_10_3_amd64 -v 10.3-RELEASE&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create (one or more) ports tree. I name the default tree &amp;quot;default&amp;quot; and if I want to experiment with, say, one of the quarterly ports trees, I&#039;d name it as such.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo poudriere ports -c -p default&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=695</id>
		<title>Poudriere in a jail</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=695"/>
		<updated>2016-08-03T13:32:32Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* Create a normal ezjail&lt;br /&gt;
&lt;br /&gt;
* Create a ZFS dataset for the jail&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o jailed=on tyktank/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Change settings in ezjail config&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_poudriere_tyknet_dk_zfs_datasets=&amp;quot;tyktank/poudriere&amp;quot;&lt;br /&gt;
export jail_poudriere_tyknet_dk_parameters=&amp;quot;children.max=100 allow.mount allow.mount.tmpfs allow.mount.devfs allow.mount.procfs allow.mount.zfs allow.mount.nullfs allow.raw_sockets allow.socket_af allow.sysvipc allow.chflags enforce_statfs=1 ip6=inherit ip4=inherit&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add needed kld modules to rc.conf (and load them manually)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
kld_list=&amp;quot;zfs aesni geom_mirror tmpfs linux linprocfs nullfs procfs fdescfs&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Install needed ports&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo portmaster www/nginx ports-mgmt/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create key and cert for signing packages&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo mkdir -p /usr/local/etc/ssl/{keys,certs}&lt;br /&gt;
$ sudo chmod 0600 /usr/local/etc/ssl/keys&lt;br /&gt;
$ sudo openssl genrsa -out /usr/local/etc/ssl/keys/poudriere.key 4096&lt;br /&gt;
$ sudo openssl rsa -in /usr/local/etc/ssl/keys/poudriere.key -pubout -out /usr/local/etc/ssl/certs/poudriere.crt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create &amp;lt;code&amp;gt;/usr/local/etc/poudriere.conf&amp;lt;/code&amp;gt; with the following contents:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ZPOOL=tyktank&lt;br /&gt;
ZROOTFS=/poudriere&lt;br /&gt;
FREEBSD_HOST=ftp://ftp.dk.freebsd.org&lt;br /&gt;
RESOLV_CONF=/etc/resolv.conf&lt;br /&gt;
BASEFS=/usr/local/poudriere&lt;br /&gt;
USE_PORTLINT=no&lt;br /&gt;
USE_TMPFS=yes&lt;br /&gt;
DISTFILES_CACHE=/usr/ports/distfiles&lt;br /&gt;
PKG_REPO_SIGNING_KEY=/usr/local/etc/pki/poudriere/poudriere.key&lt;br /&gt;
NOLINUX=yes&lt;br /&gt;
BUILDER_HOSTNAME=poudriere.tyknet.dk&lt;br /&gt;
&lt;br /&gt;
# https://gist.github.com/gynter/86ed7a6cae20927d6ef0&lt;br /&gt;
USE_MASTERMNT_HASH=yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* The last line is not a default setting but one that is neccesary due to path length restrictions combined with jails. To make it work apply the patch in the link above.&lt;br /&gt;
&lt;br /&gt;
* Create one or more jails for the versions you need to build for&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo poudriere jail -c -j freebsd_10_3_amd64 -v 10.3-RELEASE&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=694</id>
		<title>Poudriere in a jail</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=694"/>
		<updated>2016-08-03T13:12:45Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* Create a normal ezjail&lt;br /&gt;
&lt;br /&gt;
* Create a ZFS dataset for the jail&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o jailed=on tyktank/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Change settings in ezjail config&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_poudriere_tyknet_dk_zfs_datasets=&amp;quot;tyktank/poudriere&amp;quot;&lt;br /&gt;
export jail_poudriere_tyknet_dk_parameters=&amp;quot;children.max=100 allow.mount allow.mount.tmpfs allow.mount.devfs allow.mount.procfs allow.mount.zfs allow.mount.nullfs allow.raw_sockets allow.socket_af allow.sysvipc allow.chflags enforce_statfs=1 ip6=inherit ip4=inherit&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add needed kld modules to rc.conf (and load them manually)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
kld_list=&amp;quot;zfs aesni geom_mirror tmpfs linux linprocfs nullfs procfs fdescfs&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Install needed ports&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo portmaster www/nginx ports-mgmt/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create key and cert for signing packages&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo mkdir -p /usr/local/etc/ssl/{keys,certs}&lt;br /&gt;
$ sudo chmod 0600 /usr/local/etc/ssl/keys&lt;br /&gt;
$ sudo openssl genrsa -out /usr/local/etc/ssl/keys/poudriere.key 4096&lt;br /&gt;
$ sudo openssl rsa -in /usr/local/etc/ssl/keys/poudriere.key -pubout -out /usr/local/etc/ssl/certs/poudriere.crt&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Create &amp;lt;code&amp;gt;/usr/local/etc/poudriere.conf&amp;lt;/code&amp;gt; with the following contents:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ZPOOL=tyktank&lt;br /&gt;
ZROOTFS=/poudriere&lt;br /&gt;
FREEBSD_HOST=ftp://ftp.dk.freebsd.org&lt;br /&gt;
RESOLV_CONF=/etc/resolv.conf&lt;br /&gt;
BASEFS=/usr/local/poudriere&lt;br /&gt;
USE_PORTLINT=no&lt;br /&gt;
USE_TMPFS=yes&lt;br /&gt;
DISTFILES_CACHE=/usr/ports/distfiles&lt;br /&gt;
PKG_REPO_SIGNING_KEY=/usr/local/etc/pki/poudriere/poudriere.key&lt;br /&gt;
NOLINUX=yes&lt;br /&gt;
BUILDER_HOSTNAME=poudriere.tyknet.dk&lt;br /&gt;
&lt;br /&gt;
# https://gist.github.com/gynter/86ed7a6cae20927d6ef0&lt;br /&gt;
USE_MASTERMNT_HASH=yes&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* The last line is not a default setting but one that is neccesary due to path length restrictions combined with jails. To make it work apply the patch in the link above.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=693</id>
		<title>Poudriere in a jail</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=693"/>
		<updated>2016-08-03T11:03:11Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* Create a normal ezjail&lt;br /&gt;
&lt;br /&gt;
* Create a ZFS dataset for the jail&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o jailed=on tyktank/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Change settings in ezjail config&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_poudriere_tyknet_dk_zfs_datasets=&amp;quot;tyktank/poudriere&amp;quot;&lt;br /&gt;
export jail_poudriere_tyknet_dk_parameters=&amp;quot;children.max=100 allow.mount allow.mount.tmpfs allow.mount.devfs allow.mount.procfs allow.mount.zfs allow.mount.nullfs allow.raw_sockets allow.socket_af allow.sysvipc allow.chflags enforce_statfs=1 ip6=inherit ip4=inherit&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add needed kld modules to rc.conf (and load them manually)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
kld_list=&amp;quot;zfs aesni geom_mirror tmpfs linux linprocfs nullfs procfs fdescfs&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Install needed ports&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo portmaster www/nginx ports-mgmt/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=692</id>
		<title>Poudriere in a jail</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=692"/>
		<updated>2016-08-02T13:12:04Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* Create a normal ezjail&lt;br /&gt;
&lt;br /&gt;
* Create a ZFS dataset for the jail&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o jailed=on tank/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Change settings in ezjail config&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_poudriere_tyknet_dk_parameters=&amp;quot;children.max=100 allow.mount allow.mount.tmpfs allow.mount.devfs allow.mount.procfs allow.mount.zfs allow.mount.nullfs allow.raw_sockets allow.socket_af allow.sysvipc allow.chflags enforce_statfs=1 ip6=inherit ip4=inherit&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Add needed kld modules to rc.conf (and load them manually)&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
kld_list=&amp;quot;zfs aesni geom_mirror tmpfs linux linprocfs nullfs procfs fdescfs&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Install needed ports&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo portmaster www/nginx ports-mgmt/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=691</id>
		<title>Poudriere in a jail</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=691"/>
		<updated>2016-08-01T10:09:35Z</updated>

		<summary type="html">&lt;p&gt;Tykling: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* Create a normal ezjail&lt;br /&gt;
* Create a ZFS dataset for the jail&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$  sudo zfs create -o jailed=on tank/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* Change settings in ezjail config&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_poudriere_tyknet_dk_parameters=&amp;quot;children.max=100 allow.mount allow.mount.tmpfs allow.mount.devfs allow.mount.procfs allow.mount.zfs allow.mount.nullfs allow.raw_sockets allow.socket_af allow.sysvipc allow.chflags enforce_statfs=1 ip6=inherit ip4=inherit&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* Add needed kld modules to rc.conf&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
kld_list=&amp;quot;zfs aesni geom_mirror tmpfs linux linprocfs nullfs procfs fdescfs&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=690</id>
		<title>Poudriere in a jail</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Poudriere_in_a_jail&amp;diff=690"/>
		<updated>2016-08-01T07:20:20Z</updated>

		<summary type="html">&lt;p&gt;Tykling: Created page with &amp;quot;* Create a normal ezjail * Create a ZFS dataset for the jail &amp;lt;pre&amp;gt; $  sudo zfs create -o jailed=on tank/poudriere &amp;lt;/pre&amp;gt; * Change settings in ezjail config &amp;lt;pre&amp;gt; export jail_p...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* Create a normal ezjail&lt;br /&gt;
* Create a ZFS dataset for the jail&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$  sudo zfs create -o jailed=on tank/poudriere&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
* Change settings in ezjail config&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_poudriere_tyknet_dk_parameters=&amp;quot;children.max=100 allow.mount allow.mount.tmpfs allow.mount.devfs allow.mount.procfs allow.mount.zfs allow.mount.nullfs allow.raw_sockets allow.socket_af allow.sysvipc allow.chflags enforce_statfs=1 ip6=inherit ip4=inherit&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=689</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=689"/>
		<updated>2016-01-05T16:52:35Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Process Accounting */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I put the accounting data on a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo zfs create tank/root/var/account&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=688</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=688"/>
		<updated>2016-01-05T16:37:22Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Configuration */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Process Accounting ==&lt;br /&gt;
I like to enable process accounting on my jail hosts. It can be useful in a lot of situations. I run the following commands to enable it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo touch /var/account/acct&lt;br /&gt;
sudo chmod 600 /var/account/acct&lt;br /&gt;
sudo accton /var/account/acct&lt;br /&gt;
sudo sysrc accounting_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=687</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=687"/>
		<updated>2016-01-03T13:55:32Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Tor relay jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
A few steps (that should really be done by the port) are needed here:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo rm -rf /var/db/tor /var/run/tor&lt;br /&gt;
sudo mkdir -p /var/db/tor/data /var/run/tor /var/log/tor&lt;br /&gt;
sudo chown -R _tor:_tor /var/db/tor /var/log/tor /var/run/tor&lt;br /&gt;
sudo chmod -R 700 /var/db/tor&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=686</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=686"/>
		<updated>2016-01-03T11:20:01Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Base jails */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible.&lt;br /&gt;
* Finally I like to run a tor relay on each box to spend any excess resources (cpu, memory, bandwidth) on something nice. As long as I don&#039;t run an exit node I can do this completely without any risk of complaints from the provider.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure each of these &amp;quot;base jails&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
== Tor relay jail ==&lt;br /&gt;
The Tor relay needs a public IP. It also needs the &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;security/tor&amp;lt;/code&amp;gt; ports built (from ports, not packages, to ensure Tor is built with a recent OpenSSL to speed up ECDH). I put the following into the &amp;lt;code&amp;gt;/usr/local/etc/tor/torrc&amp;lt;/code&amp;gt; file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Log notice file /var/log/tor/notices.log&lt;br /&gt;
ORPort 443 NoListen&lt;br /&gt;
ORPort 9090 NoAdvertise&lt;br /&gt;
Address torrelay.bong.tyknet.dk&lt;br /&gt;
Nickname TykRelay01&lt;br /&gt;
ContactInfo Thomas Steen Rasmussen / Tykling &amp;lt;thomas@gibfest.dk&amp;gt; (PGP: 0x772FF77F0972FA58)&lt;br /&gt;
DirPort 80 NoListen&lt;br /&gt;
DirPort 9091 NoAdvertise&lt;br /&gt;
ExitPolicy reject *:*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Changing the &amp;lt;code&amp;gt;Address&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Nickname&amp;lt;/code&amp;gt; depending on the server. &lt;br /&gt;
&lt;br /&gt;
I also add the following line to the jail hosts &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; to make it impossible to predict IP IDs from the server:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
net.inet.ip.random_id=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I redirect TCP ports 9090 and 9091 to ports 443 and 80 in the jail in &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep tor /etc/pf.conf&lt;br /&gt;
torv4=&amp;quot;85.235.250.88&amp;quot;&lt;br /&gt;
torv6=&amp;quot;2a01:3a0:1:1900:85:235:250:88&amp;quot;&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 443 -&amp;gt; $torv4 port 9090&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 443 -&amp;gt; $torv6 port 9090&lt;br /&gt;
rdr on $if inet proto tcp from any to $torv4 port 80 -&amp;gt; $torv4 port 9091&lt;br /&gt;
rdr on $if inet6 proto tcp from any to $torv6 port 80 -&amp;gt; $torv6 port 9091&lt;br /&gt;
pass in quick on { $if, $jailif } proto tcp from any to { $torv4 $torv6 } port { 9090, 9091 }&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=685</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=685"/>
		<updated>2015-10-26T10:43:55Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Syslog jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and my jail flavours default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so all the jails send their syslog messages to the syslog jail:&lt;br /&gt;
&amp;lt;pre&amp;gt;*.*                                             @10.0.0.1&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=684</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=684"/>
		<updated>2015-10-26T10:41:20Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Syslog jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
... and of course the jails default &amp;lt;code&amp;gt;/etc/syslog.conf&amp;lt;/code&amp;gt; all get this line so they log to the syslog jail:&lt;br /&gt;
&amp;lt;code&amp;gt;*.*                                             @10.0.0.1&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=683</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=683"/>
		<updated>2015-10-26T10:34:19Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Syslog jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I also remove the line: &amp;lt;code&amp;gt;destination console { file(&amp;quot;/dev/console&amp;quot;); };&amp;lt;/code&amp;gt; since the jail does not have access to &amp;lt;code&amp;gt;/dev/console&amp;lt;/code&amp;gt;. I also remove any corresponding &amp;lt;code&amp;gt;log&amp;lt;/code&amp;gt; statements that use &amp;lt;code&amp;gt;destination(console);&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=682</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=682"/>
		<updated>2015-09-08T04:38:35Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Postgres jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And ofcourse the standard &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=681</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=681"/>
		<updated>2015-09-06T20:18:03Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Postgres jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=680</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=680"/>
		<updated>2015-09-06T19:22:58Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Postgres jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
While I&#039;m there I also change the jails &amp;lt;code&amp;gt;# PROVIDE:&amp;lt;/code&amp;gt; line to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
* Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&lt;br /&gt;
* Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
* Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&lt;br /&gt;
* Add &amp;lt;code&amp;gt;postgres&amp;lt;/code&amp;gt; to the jails &amp;lt;code&amp;gt;# REQUIRE:&amp;lt;/code&amp;gt; line in the ezjail config file&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=679</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=679"/>
		<updated>2015-09-06T18:47:56Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Syslog jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=678</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=678"/>
		<updated>2015-09-06T18:06:29Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Syslog jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
This jail gets the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line in it&#039;s ezjail config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: syslog&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package using &amp;lt;code&amp;gt;pkg install syslog-ng&amp;lt;/code&amp;gt; and then add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=677</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=677"/>
		<updated>2015-09-06T17:49:24Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Base jails */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* I have a reverse webproxy jail since many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
This jail needs to be started first since the rest of the jails need it for DNS. ezjail runs rcorder on the config files in &amp;lt;code&amp;gt;/usr/local/etc/ezjail&amp;lt;/code&amp;gt; which means I can use the normal &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; to control the jail dependencies. I change the ezjail config for my DNS jail to have the following &amp;lt;code&amp;gt;PROVIDE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# PROVIDE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the jails all get the following &amp;lt;code&amp;gt;REQUIRE:&amp;lt;/code&amp;gt; line:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# REQUIRE: dns&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package and add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=676</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=676"/>
		<updated>2015-09-06T16:40:52Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Syslog jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* Many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I also have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package and add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I use the following options, YMMV:&lt;br /&gt;
&amp;lt;code&amp;gt;options { chain_hostnames(off); flush_lines(0); threaded(yes); use_fqdn(yes); keep_hostname(no); use_dns(yes); stats-freq(60); stats-level(0); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp()&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* I add a new destination:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
destination loghost{&lt;br /&gt;
            syslog(&lt;br /&gt;
                &amp;quot;syslog.tyktech.dk&amp;quot;&lt;br /&gt;
                transport(&amp;quot;tls&amp;quot;)&lt;br /&gt;
                port(1999)&lt;br /&gt;
                tls(peer-verify(required-untrusted))&lt;br /&gt;
                localip(&amp;quot;10.0.0.1&amp;quot;)&lt;br /&gt;
                log-fifo-size(10000)&lt;br /&gt;
            );&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Finally tell syslog-ng to send all logdata from &amp;lt;code&amp;gt;jailsrc&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;loghost&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;code&amp;gt;log { source(jailsrc); destination(loghost); flags(flow_control); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=675</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=675"/>
		<updated>2015-09-06T16:32:02Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Syslog jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* Many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I also have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package and add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=674</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=674"/>
		<updated>2015-09-06T16:31:38Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Syslog jail */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* Many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I also have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package and add a few things to the default config:&lt;br /&gt;
&lt;br /&gt;
* I remove &amp;lt;code&amp;gt;udp&amp;lt;/code&amp;gt; from the default source and define a new source:&lt;br /&gt;
&amp;lt;code&amp;gt;source jailsrc { udp(ip(&amp;quot;10.0.0.1&amp;quot;)); udp6(ip(&amp;quot;w:x:y:z:10::1&amp;quot;)); };&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=673</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=673"/>
		<updated>2015-09-06T13:02:38Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Base jails */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
* I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
* Many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
* I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
* I also have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package and add a few things to the default config.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=672</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=672"/>
		<updated>2015-09-06T13:00:09Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Base jails */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services, so:&lt;br /&gt;
- I have a postgres jail on each jail host, so I only need to maintain one postgres server. &lt;br /&gt;
- Many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. &lt;br /&gt;
- I also have a syslog server on each jailhost to collect syslog from all the jails and send them to my central syslog server.&lt;br /&gt;
- I also have a DNS jail with a caching DNS server which is used by all the jails.&lt;br /&gt;
&lt;br /&gt;
This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== Syslog jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I use &amp;lt;code&amp;gt;syslog-ng&amp;lt;/code&amp;gt; for this. I install the package and add a few things to the default config.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== DNS jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address.&lt;br /&gt;
&lt;br /&gt;
I am used to using bind but unbound is just as good. Use whatever you are comfortable with. Make sure you permit DNS traffic from the other jails to the DNS jail.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=671</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=671"/>
		<updated>2015-07-15T05:36:05Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Staying up-to-date */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services - they all need firewalling and backup, but some jails also need a database. So I always have a postgres jail on each jail host, so I only need to maintain one postgres server. Also, many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=670</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=670"/>
		<updated>2015-07-15T05:33:19Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Caveats */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services - they all need firewalling and backup, but some jails also need a database. So I always have a postgres jail on each jail host, so I only need to maintain one postgres server. Also, many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world and kernel version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
	<entry>
		<id>https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=669</id>
		<title>Ezjail host</title>
		<link rel="alternate" type="text/html" href="https://tyk.wiki/index.php?title=Ezjail_host&amp;diff=669"/>
		<updated>2015-07-15T05:32:23Z</updated>

		<summary type="html">&lt;p&gt;Tykling: /* Add the periodic script */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Background =&lt;br /&gt;
This is my personal checklist for when I am setting up a new ezjail host. I like my jail hosts configured in a very specific way. There is a good chance that what is right for me is not right for you. As always, YMMV.&lt;br /&gt;
&lt;br /&gt;
Also note that I talk a lot about the German hosting provider Hetzner, if you are using another provider or you are doing this at home, just ignore the Hetzner specific stuff. Much of the content here can be used with little or no changes outside Hetzner.&lt;br /&gt;
&lt;br /&gt;
= Installation =&lt;br /&gt;
== OS install with mfsbsd ==&lt;br /&gt;
After receiving the server from Hetzner I boot it using the rescue system which puts me at an mfsbsd prompt via SSH. This is perfect for installing a zfs-only server.&lt;br /&gt;
&lt;br /&gt;
=== Changes to zfsinstall ===&lt;br /&gt;
I edit the &#039;&#039;zfsinstall&#039;&#039; script &amp;lt;code&amp;gt;/root/bin/zfsinstall&amp;lt;/code&amp;gt; and add &amp;quot;usr&amp;quot; to &#039;&#039;FS_LIST&#039;&#039; near the top of the script. I do this because I like to have /usr as a seperate ZFS dataset.&lt;br /&gt;
&lt;br /&gt;
=== Check disks ===&lt;br /&gt;
I create a small zpool using just 30gigs, enough to confortably install the base OS and so on. The rest of the diskspace will be used for GELI which will have the other zfs pool on top. This encrypted zpool will house the actual jails and data. This setup allows me to have all the important data encrypted, while allowing the physical server to boot without human intervention like full disk encryption would require.&lt;br /&gt;
&lt;br /&gt;
Note that the disks in this server are not new, they have been used for around two years (18023 hours/24 = 702 days):&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# grep &amp;quot;ada[0-9]:&amp;quot; /var/run/dmesg.boot | grep &amp;quot;MB &amp;quot;&lt;br /&gt;
ada0: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
ada1: 1907729MB (3907029168 512 byte sectors: 16H 63S/T 16383C)&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada0 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]# smartctl -a /dev/ada1 | grep Power_On_Hours&lt;br /&gt;
  9 Power_On_Hours          0x0032   096   096   000    Old_age   Always       -       18023&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Destroy existing partitions ===&lt;br /&gt;
Any existing partitions need to be deleted first. This can be done with the &#039;&#039;destroygeom&#039;&#039; command like shown below:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# destroygeom -d ada0 -d ada1&lt;br /&gt;
Destroying geom ada0:&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
Destroying geom ada1:&lt;br /&gt;
    Deleting partition 1 ... done&lt;br /&gt;
    Deleting partition 2 ... done&lt;br /&gt;
    Deleting partition 3 ... done&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Install FreeBSD ===&lt;br /&gt;
Installing FreeBSD with mfsbsd is easy. I run the below command, adjusting the release I want to install of course:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zfsinstall -d ada0 -d ada1 -r mirror -z 30G -t /nfs/mfsbsd/10.0-release-amd64.tbz&lt;br /&gt;
Creating GUID partitions on ada0 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada0 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada0  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating GUID partitions on ada1 ... done&lt;br /&gt;
Configuring ZFS bootcode on ada1 ... done&lt;br /&gt;
=&amp;gt;        34  3907029101  ada1  GPT  (1.8T)&lt;br /&gt;
          34        2014        - free -  (1M)&lt;br /&gt;
        2048         128     1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560     2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  3844112399        - free -  (1.8T)&lt;br /&gt;
&lt;br /&gt;
Creating ZFS pool tank on ada0p2 ada1p2 ... done&lt;br /&gt;
Creating tank root partition: ... done&lt;br /&gt;
Creating tank partitions: var tmp usr ... done&lt;br /&gt;
Setting bootfs for tank to tank/root ... done&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
tank            270K  29.3G    31K  none&lt;br /&gt;
tank/root       127K  29.3G    34K  /mnt&lt;br /&gt;
tank/root/tmp    31K  29.3G    31K  /mnt/tmp&lt;br /&gt;
tank/root/usr    31K  29.3G    31K  /mnt/usr&lt;br /&gt;
tank/root/var    31K  29.3G    31K  /mnt/var&lt;br /&gt;
Extracting FreeBSD distribution ... done&lt;br /&gt;
Writing /boot/loader.conf... done&lt;br /&gt;
Writing /etc/fstab...Writing /etc/rc.conf... done&lt;br /&gt;
Copying /boot/zfs/zpool.cache ... done&lt;br /&gt;
&lt;br /&gt;
Installation complete.&lt;br /&gt;
The system will boot from ZFS with clean install on next reboot&lt;br /&gt;
&lt;br /&gt;
You may type &amp;quot;chroot /mnt&amp;quot; and make any adjustments you need.&lt;br /&gt;
For example, change the root password or edit/create /etc/rc.conf for&lt;br /&gt;
for system services.&lt;br /&gt;
&lt;br /&gt;
WARNING - Don&#039;t export ZFS pool &amp;quot;tank&amp;quot;!&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Post install configuration (before reboot) ===&lt;br /&gt;
Before rebooting into the installed FreeBSD I need to make certain I can reach the server through SSH after the reboot. This means:&lt;br /&gt;
# Adding network settings to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Adding sshd_enable=&amp;quot;YES&amp;quot; to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Change &#039;&#039;PermitRootLogin&#039;&#039; to &#039;&#039;Yes&#039;&#039; in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt; &#039;&#039;Note: In the current This is now the default in the zfsinstall image that Hetzner provides&#039;&#039;&lt;br /&gt;
# Add nameservers to &amp;lt;code&amp;gt;/etc/resolv.conf&amp;lt;/code&amp;gt;&lt;br /&gt;
# Finally I set the root password. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;All of these steps are essential if I am going to have any chance of logging in after reboot.&#039;&#039;&#039; Most of these changes can be done from the mfsbsd shell but the password change requires &#039;&#039;chroot&#039;&#039; into the newly installed environment.&lt;br /&gt;
&lt;br /&gt;
I use the &#039;&#039;chroot&#039;&#039; command but start another shell as bash is not installed in /mnt:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# chroot /mnt/ csh&lt;br /&gt;
rescue# ee /etc/rc.conf&lt;br /&gt;
rescue# ee /etc/ssh/sshd_config&lt;br /&gt;
rescue# passwd&lt;br /&gt;
New Password:&lt;br /&gt;
Retype New Password:&lt;br /&gt;
rescue#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So, the network settings are sorted, root password is set, and root is permitted to ssh in. Time to reboot (this is the exciting part). &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Remember to use &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; and not &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; when you reboot. &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; performs the proper shutdown process including rc.d scripts and disk buffer flushing. &amp;lt;code&amp;gt;reboot&amp;lt;/code&amp;gt; is the &amp;quot;bigger hammer&amp;quot; to use when something is preventing &amp;lt;code&amp;gt;shutdown -r now&amp;lt;/code&amp;gt; from working.&lt;br /&gt;
&lt;br /&gt;
== Basic config after first boot ==&lt;br /&gt;
If the server boots without any problems, I do some basic configuration before I continue with the disk partitioning.&lt;br /&gt;
&lt;br /&gt;
=== Timezone ===&lt;br /&gt;
I run the command &amp;lt;code&amp;gt;tzsetup&amp;lt;/code&amp;gt; to set the proper timezone, and set the time using &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; if neccesary.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: The current hetzner freebsd image has the timezone set to CEST, I like my servers configured as UTC&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Basic ports ===&lt;br /&gt;
I also add some basic ports with &amp;lt;code&amp;gt;pkg&amp;lt;/code&amp;gt; so I can get screen etc. up and running as soon as possible:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# pkg install bash screen sudo portmaster&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I then add the following to &amp;lt;code&amp;gt;/usr/local/etc/portmaster.rc&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALWAYS_SCRUB_DISTFILES=dopt&lt;br /&gt;
PM_DEL_BUILD_ONLY=pm_dbo&lt;br /&gt;
SAVE_SHARED=wopt&lt;br /&gt;
PM_LOG=/var/log/portmaster.log&lt;br /&gt;
PM_IGNORE_FAILED_BACKUP_PACKAGE=pm_ignore_failed_backup_package&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
An explanation of these options can be found on the [[Portmaster]] page.&lt;br /&gt;
&lt;br /&gt;
After a &amp;lt;code&amp;gt;rehash&amp;lt;/code&amp;gt; and adding my non-root user with &amp;lt;code&amp;gt;adduser&amp;lt;/code&amp;gt;, I am ready to continue with the disk configuration. I also remember to disable root login in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Further disk configuration ==&lt;br /&gt;
After the reboot into the installed FreeBSD environment, I need to do some further disk configuration.&lt;br /&gt;
=== Create swap partitions ===&lt;br /&gt;
Swap-on-zfs is not a good idea for various reasons. To keep my swap encrypted but still off zfs I use geli onetime encryption. To avoid problems if a disk dies I also use gmirror. First I add the partitions with gpart:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada0&lt;br /&gt;
ada0p3 added&lt;br /&gt;
$ sudo gpart add -t freebsd-swap -s 10G /dev/ada1&lt;br /&gt;
ada1p3 added&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I make sure gmirror is loaded, and loaded on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf geom_mirror_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
$ sudo kldload geom_mirror&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then I create the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo gmirror label swapmirror /dev/ada0p3 /dev/ada1p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I add the following line to &amp;lt;code&amp;gt;/etc/fstab&amp;lt;/code&amp;gt; to get encrypted swap on top of the gmirror:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/dev/mirror/swapmirror.eli  none    swap    sw,keylen=256,sectorsize=4096     0       0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I can enable the new swap partition right away:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo swapon /dev/mirror/swapmirror.eli&lt;br /&gt;
$ swapinfo&lt;br /&gt;
Device          1K-blocks     Used    Avail Capacity&lt;br /&gt;
/dev/mirror/swapmirror.eli   8388604        0  8388604     0%&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI partitions ===&lt;br /&gt;
First I create the partitions to hold the geli devices:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada0&lt;br /&gt;
ada0p4 added&lt;br /&gt;
$ sudo gpart add -t freebsd-ufs ada1&lt;br /&gt;
ada1p4 added&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I add them as &amp;lt;code&amp;gt;freebsd-ufs&amp;lt;/code&amp;gt; type partitions, as there is no dedicated &amp;lt;code&amp;gt;freebsd-geli&amp;lt;/code&amp;gt; type.&lt;br /&gt;
&lt;br /&gt;
=== Create GELI key ===&lt;br /&gt;
To create a GELI key I copy some data from &amp;lt;code&amp;gt;/dev/random&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo dd if=/dev/random of=/root/geli.key bs=256k count=1&lt;br /&gt;
1+0 records in&lt;br /&gt;
1+0 records out&lt;br /&gt;
262144 bytes transferred in 0.003347 secs (78318372 bytes/sec)&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create GELI volumes ===&lt;br /&gt;
I create the GELI volumes with 4k blocksize and 256bit AES encryption:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada0p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada0p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada0p4.eli /dev/ada0p4&lt;br /&gt;
&lt;br /&gt;
$ sudo geli init -s 4096 -K /root/geli.key -l 256 /dev/ada1p4&lt;br /&gt;
Enter new passphrase:&lt;br /&gt;
Reenter new passphrase:&lt;br /&gt;
&lt;br /&gt;
Metadata backup can be found in /var/backups/ada1p4.eli and&lt;br /&gt;
can be restored with the following command:&lt;br /&gt;
&lt;br /&gt;
        # geli restore /var/backups/ada1p4.eli /dev/ada1p4&lt;br /&gt;
&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Enable AESNI ===&lt;br /&gt;
Most Intel CPUs have hardware acceleration of AES which helps a lot with GELI performance. I load the &amp;lt;code&amp;gt;aesni&amp;lt;/code&amp;gt; module during boot from &amp;lt;code&amp;gt;/boot/loader.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo sysrc -f /boot/loader.conf aesni_load=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Attach GELI volumes ===&lt;br /&gt;
Now I just need to attach the GELI volumes before I am ready to create the second zpool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada0p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$ sudo geli attach -k /root/geli.key /dev/ada1p4&lt;br /&gt;
Enter passphrase:&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create second zpool ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zpool create gelipool mirror /dev/ada0p4.eli /dev/ada1p4.eli&lt;br /&gt;
$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME            STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool        ONLINE       0     0     0&lt;br /&gt;
          mirror-0      ONLINE       0     0     0&lt;br /&gt;
            ada0p4.eli  ONLINE       0     0     0&lt;br /&gt;
            ada1p4.eli  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: none requested&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS filesystems on the new zpool ===&lt;br /&gt;
The last remaining thing is to create a filesystem in the new zfs pool:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME            USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool        624K  3.54T   144K  /gelipool&lt;br /&gt;
tank            704M  28.6G    31K  none&lt;br /&gt;
tank/root       704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp    38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr   291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var   505K  28.6G   505K  /var&lt;br /&gt;
$ sudo zfs set mountpoint=none gelipool&lt;br /&gt;
$ sudo zfs set compression=on gelipool&lt;br /&gt;
$ sudo zfs create -o mountpoint=/usr/jails gelipool/jails&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME             USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool         732K  3.54T   144K  none&lt;br /&gt;
gelipool/jails   144K  3.54T   144K  /usr/jails&lt;br /&gt;
tank             704M  28.6G    31K  none&lt;br /&gt;
tank/root        704M  28.6G   413M  /&lt;br /&gt;
tank/root/tmp     38K  28.6G    38K  /tmp&lt;br /&gt;
tank/root/usr    291M  28.6G   291M  /usr&lt;br /&gt;
tank/root/var    505K  28.6G   505K  /var&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Disable atime ===&lt;br /&gt;
One last thing I like to do is to disable &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; or &#039;&#039;access time&#039;&#039; on the filesystem. Access times are recorded every time a file is read, and while this can have it&#039;s use cases, I never use it. Disabling it means a lot fewer write operations, as a read operation doesn&#039;t automatically include a write operation when &amp;lt;code&amp;gt;atime&amp;lt;/code&amp;gt; is disabled. Disabling it is easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs set atime=off tank&lt;br /&gt;
$ sudo zfs set atime=off gelipool&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The next things are post-install configuration stuff like OS upgrade, ports, firewall and so on. The basic install is finished \o/&lt;br /&gt;
&lt;br /&gt;
=== Reserved space ===&lt;br /&gt;
Running out of space in ZFS is bad. Stuff will run slowly and may stop working entirely until some space is freed. The problem is that ZFS is a journalled filesystem which means that all writes, even a deletion, requires writing data to the disk. I&#039;ve more than once wound up in a situation where I couldn&#039;t delete file to free up diskspace because the disk was full. &lt;br /&gt;
&lt;br /&gt;
Sometimes this can be resolved by overwriting a large file, like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ echo &amp;gt; /path/to/a/large/file&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will overwrite the file thereby freeing up some space, but sometimes even this is not possible. This is where reserved space comes in. I create a new filesystem in each pool, set them readonly and without a mountpoint, and with 1G reserved each:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on gelipool/reserved&lt;br /&gt;
$ sudo zfs create -o mountpoint=none -o reservation=1G -o readonly=on tank/reserved&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If I run out of space for some reason, I can just delete the dataset, or unset the &amp;lt;code&amp;gt;reserved&amp;lt;/code&amp;gt; property, and I immediately have 1G diskspace available. Yay!&lt;br /&gt;
&lt;br /&gt;
= Ports =&lt;br /&gt;
== Installing the ports tree ==&lt;br /&gt;
I need to bootstrap the ports system, I use &amp;lt;code&amp;gt;portsnap&amp;lt;/code&amp;gt; as it is way faster than using c(v)sup. Initially I run &amp;lt;code&amp;gt;portsnap fetch extract&amp;lt;/code&amp;gt; and when I need to update the tree later I use &amp;lt;code&amp;gt;portsnap fetch update&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== smartd ==&lt;br /&gt;
I install smartd to monitor the disks for problems:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo pkg install smartmontools&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I create the file &amp;lt;code&amp;gt;/usr/local/etc/smartd.conf&amp;lt;/code&amp;gt; and add this line to it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DEVICESCAN -a -m thomas@gibfest.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes smartd monitor all disks and send me an email if it finds an error.&lt;br /&gt;
&lt;br /&gt;
Remember to enable smartd in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; and start it:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc smartd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo service smartd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== openntpd ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;net/openntpd&amp;lt;/code&amp;gt; to keep the clock in sync. I find this a lot easier to configure than the base ntpd.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install openntpd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I enable &amp;lt;code&amp;gt;openntpd&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc openntpd_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and add a one line config file:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep -v &amp;quot;^#&amp;quot; /usr/local/etc/ntpd.conf | grep -v &amp;quot;^$&amp;quot;&lt;br /&gt;
servers de.pool.ntp.org&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
sync the clock and start openntpd:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo ntpdate de.pool.ntp.org&lt;br /&gt;
sudo service openntpd start&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ntpdate ==&lt;br /&gt;
I also enable &amp;lt;code&amp;gt;ntpdate&amp;lt;/code&amp;gt; to help set the clock after a reboot. I add the following two lines to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc ntpdate_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc ntpdate_hosts=&amp;quot;de.pool.ntp.org&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Upgrade OS (buildworld) =&lt;br /&gt;
I usually run -STABLE on my hosts, which means I need to build and install a new world and kernel. I also like having [http://www.freebsd.org/cgi/man.cgi?query=rctl rctl] available on my jail hosts, so I can limit jail ressources in all kinds of neat ways. I also like having DTRACE available. Additionally I also need the built world to populate ezjails basejail.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: I will need to update the host and the jails many times during the lifespan of this server, which is likely &amp;gt; 2-3 years. As new security problems are found or features are added that I want, I will update host and jails. There is a section about staying up to date later in this page. This section (the one you are reading now) only covers the OS update I run right after installing the server.&lt;br /&gt;
&lt;br /&gt;
== Fetching sources ==&lt;br /&gt;
I install &amp;lt;code&amp;gt;subversion&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install subversion&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
and then get the sources:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo svn checkout https://svn0.eu.freebsd.org/base/stable/10/ /usr/src/&lt;br /&gt;
Error validating server certificate for &#039;https://svn0.eu.freebsd.org:443&#039;:&lt;br /&gt;
 - The certificate is not issued by a trusted authority. Use the&lt;br /&gt;
   fingerprint to validate the certificate manually!&lt;br /&gt;
Certificate information:&lt;br /&gt;
 - Hostname: svnmir.bme.FreeBSD.org&lt;br /&gt;
 - Valid: from Jun 29 12:24:17 2013 GMT until Jun 29 12:24:17 2015 GMT&lt;br /&gt;
 - Issuer: clusteradm, FreeBSD.org, CA, US(clusteradm@FreeBSD.org)&lt;br /&gt;
 - Fingerprint: 39:B0:53:35:CE:60:C7:BB:00:54:96:96:71:10:94:BB:CE:1C:07:A7&lt;br /&gt;
(R)eject, accept (t)emporarily or accept (p)ermanently? p&lt;br /&gt;
A    /usr/src/sys&lt;br /&gt;
A    /usr/src/sys/arm&lt;br /&gt;
A    /usr/src/sys/arm/ti&lt;br /&gt;
A    /usr/src/sys/arm/ti/cpsw&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x&lt;br /&gt;
A    /usr/src/sys/arm/ti/usb&lt;br /&gt;
A    /usr/src/sys/arm/ti/twl&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_cpuid.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_mmchs.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_i2c.h&lt;br /&gt;
A    /usr/src/sys/arm/ti/am335x/am335x_pwm.c&lt;br /&gt;
A    /usr/src/sys/arm/ti/ti_sdma.c&lt;br /&gt;
&amp;lt;snip&amp;gt;&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_cleaner.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_subr.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/bmap.h&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_vfsops.c&lt;br /&gt;
A    /usr/src/sys/fs/nandfs/nandfs_sufile.c&lt;br /&gt;
 U   /usr/src&lt;br /&gt;
Checked out revision 282899.&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This takes a while the first time, but subsequent runs are much faster.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; The reason I use the full Subversion port instead of just svnup is that using full SVN client means that you get the SVN revision in &amp;lt;code&amp;gt;uname -a&amp;lt;/code&amp;gt; when checking out code with the full SVN client.&lt;br /&gt;
&lt;br /&gt;
== Create kernel config ==&lt;br /&gt;
After the sources finish downloading, I create a new kernel config file &amp;lt;code&amp;gt;/etc/TYKJAIL&amp;lt;/code&amp;gt; with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
include GENERIC&lt;br /&gt;
ident TYKJAIL&lt;br /&gt;
&lt;br /&gt;
#rctl&lt;br /&gt;
options RACCT&lt;br /&gt;
options RCTL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
I then create a symlink to the kernel config file in /etc/:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# ln -s /etc/TYKJAIL /usr/src/sys/amd64/conf/&lt;br /&gt;
# ls -l /usr/src/sys/amd64/conf/TYKJAIL &lt;br /&gt;
lrwxr-xr-x  1 root  wheel  9 Jul 22 16:14 /usr/src/sys/amd64/conf/TYKJAIL -&amp;gt; /etc/TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally I enable the kernel config in &amp;lt;code&amp;gt;/etc/make.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /etc/make.conf&lt;br /&gt;
KERNCONF=TYKJAIL&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Building world and kernel ==&lt;br /&gt;
Finally I start the build. I use -j to start one thread per core in the system. &amp;lt;code&amp;gt;sysctl hw.ncpu&amp;lt;/code&amp;gt; shows the number of available cores:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# sysctl hw.ncpu&lt;br /&gt;
hw.ncpu: 12&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To build the new system:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# time (sudo make -j$(sysctl -n hw.ncpu) buildworld &amp;amp;&amp;amp; sudo make -j$(sysctl -n hw.ncpu) kernel) &amp;amp;&amp;amp; date&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After the build finishes, reboot and run mergemaster, installworld, and mergemaster again:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# cd /usr/src/&lt;br /&gt;
# sudo mergemaster -pFUi &amp;amp;&amp;amp; sudo make installworld &amp;amp;&amp;amp; sudo mergemaster -FUi&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;DO NOT OVERWRITE /etc/group AND /etc/master.passwd AND OTHER CRITICAL FILES!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Reboot after the final mergemaster completes, and boot into the newly built world.&lt;br /&gt;
&lt;br /&gt;
= Preparing ezjail =&lt;br /&gt;
ezjail needs to be installed and a bit of configuration is also needed, in addition to bootstrapping &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;/usr/jails/newjail&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Installing ezjail ==&lt;br /&gt;
Just install it with pkg:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo pkg install ezjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuring ezjail ==&lt;br /&gt;
Then I go edit the ezjail config file &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; and add/change these three lines near the bottom:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_use_zfs=&amp;quot;YES&amp;quot;&lt;br /&gt;
ezjail_jailzfs=&amp;quot;gelipool/jails&amp;quot;&lt;br /&gt;
ezjail_use_zfs_for_jails=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes ezjail use seperate zfs datasets under &amp;lt;code&amp;gt;gelipool/jails&amp;lt;/code&amp;gt; for the &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;, as well as for each jail created. &amp;lt;code&amp;gt;ezjail_use_zfs_for_jails&amp;lt;/code&amp;gt; is supported since ezjail 3.2.2.&lt;br /&gt;
&lt;br /&gt;
== Bootstrapping ezjail ==&lt;br /&gt;
Finally I populate &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt; from the world I build earlier:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo ezjail-admin update -i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The last line of the output is a message saying:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Note: a non-standard /etc/make.conf was copied to the template jail in order to get the ports collection running inside jails.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This is because ezjail defaults to symlinking the ports collection in the same way it symlinks the basejail. I prefer having seperate/individual ports collections in each of my jails though, so I remove the symlink and make.conf from newjail:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo rm /usr/jails/newjail/etc/make.conf /usr/jails/newjail/usr/ports /usr/jails/newjail/usr/src&lt;br /&gt;
$ sudo mkdir /usr/jails/newjail/usr/src&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ZFS goodness ==&lt;br /&gt;
Note that ezjail has created two new ZFS datasets to hold &amp;lt;code&amp;gt;basejail&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;newjail&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -r gelipool/jails&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
gelipool/jails            239M  3.54T   476K  /usr/jails&lt;br /&gt;
gelipool/jails/basejail   236M  3.54T   236M  /usr/jails/basejail&lt;br /&gt;
gelipool/jails/newjail   3.10M  3.54T  3.10M  /usr/jails/newjail&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== ezjail flavours ==&lt;br /&gt;
ezjail has a pretty awesome feature that makes it possible to create templates or &#039;&#039;flavours&#039;&#039; which apply common settings when creating a new jail. I always have a &#039;&#039;basic&#039;&#039; flavour which adds a user for me, installs an SSH key, adds a few packages like bash, screen, sudo and portmaster - and configures those packages. Basically, everything I find myself doing over and over again every time I create a new jail.&lt;br /&gt;
&lt;br /&gt;
It is also possible, of course, to create more advanced flavours, I&#039;ve had one that installs a complete nginx+php-fpm server with all the neccesary packages and configs.&lt;br /&gt;
&lt;br /&gt;
ezjail flavours are technically pretty simple. By default, they are located in the same place as &#039;&#039;basejail&#039;&#039; and &#039;&#039;newjail&#039;&#039;, and ezjail comes with an example flavour to get you started. Basically a flavour is a file/directory hierachy which is copied to the jail, and a shell script called &#039;&#039;ezjail.flavour&#039;&#039; which is run once, the first time the jail is started, and then deleted.&lt;br /&gt;
&lt;br /&gt;
For reference, I&#039;ve included my basic flavour here. First is a listing of the files included in the flavour, and then the &#039;&#039;ezjail.flavour&#039;&#039; script which performs tasks beyond copying config files.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ find /usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic&lt;br /&gt;
/usr/jails/flavours/tykbasic/ezjail.flavour&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/portmaster.rc&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/local/etc/sudoers&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.ssh/authorized_keys&lt;br /&gt;
/usr/jails/flavours/tykbasic/usr/home/tykling/.screenrc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/fstab&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/rc.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/periodic.conf&lt;br /&gt;
/usr/jails/flavours/tykbasic/etc/resolv.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, the flavour contains files like &#039;&#039;/etc/resolv.conf&#039;&#039; and other stuff to make the jail work. The name of the flavour here is &#039;&#039;tykbasic&#039;&#039; which means that if I want a file to end up in &#039;&#039;/usr/home/tykling&#039;&#039; after the flavour has been applied, I need to put that file in the folder &#039;&#039;/usr/jails/flavours/tykbasic/usr/home/tykling/&#039;&#039; - &#039;&#039;&#039;remember to also chown the files in the flavour appropriately&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Finally, my &#039;&#039;ezjail.flavour&#039;&#039; script looks like so:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
#&lt;br /&gt;
# BEFORE: DAEMON&lt;br /&gt;
#&lt;br /&gt;
# ezjail flavour example&lt;br /&gt;
&lt;br /&gt;
# Timezone&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
ln -s /usr/share/zoneinfo/Europe/Copenhagen /etc/localtime&lt;br /&gt;
&lt;br /&gt;
# Groups&lt;br /&gt;
#########&lt;br /&gt;
#&lt;br /&gt;
pw groupadd -q -n tykling&lt;br /&gt;
&lt;br /&gt;
# Users&lt;br /&gt;
########&lt;br /&gt;
#&lt;br /&gt;
# To generate a password hash for use here, do:&lt;br /&gt;
# openssl passwd -1 &amp;quot;the password&amp;quot;&lt;br /&gt;
echo -n &#039;$1$L/fC0UrO$bi65/BOIAtMkvluDEDCy31&#039; | pw useradd -n tykling -u 1001 -s /bin/sh -m -d /usr/home/tykling -g tykling -c &#039;tykling&#039; -H 0&lt;br /&gt;
&lt;br /&gt;
# Packages&lt;br /&gt;
###########&lt;br /&gt;
#&lt;br /&gt;
env ASSUME_ALWAYS_YES=YES pkg bootstrap&lt;br /&gt;
pkg install -y bash&lt;br /&gt;
pkg install -y sudo&lt;br /&gt;
pkg install -y portmaster&lt;br /&gt;
pkg install -y screen&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#change shell to bash&lt;br /&gt;
chsh -s bash tykling&lt;br /&gt;
&lt;br /&gt;
#update /etc/aliases&lt;br /&gt;
echo &amp;quot;root:   thomas@gibfest.dk&amp;quot; &amp;gt;&amp;gt; /etc/aliases&lt;br /&gt;
newaliases&lt;br /&gt;
&lt;br /&gt;
#remove adjkerntz from crontab&lt;br /&gt;
cat /etc/crontab | grep -E -v &amp;quot;(Adjust the time|adjkerntz)&amp;quot; &amp;gt; /etc/crontab.new&lt;br /&gt;
mv /etc/crontab.new /etc/crontab&lt;br /&gt;
&lt;br /&gt;
#remove ports symlink&lt;br /&gt;
rm /usr/ports&lt;br /&gt;
&lt;br /&gt;
# create symlink to /usr/home in / (adduser defaults to /usr/username as homedir)&lt;br /&gt;
ln -s /usr/home /home&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Creating a flavour is easy: just create a folder under &amp;lt;code&amp;gt;/usr/jails/flavours/&amp;lt;/code&amp;gt; that has the name of the flavour, and start adding files and folders there. The ezjail.flavour script should be placed in the root (see the example further up the page).&lt;br /&gt;
&lt;br /&gt;
Finally I add the following to &amp;lt;code&amp;gt;/usr/local/etc/ezjail.conf&amp;lt;/code&amp;gt; to make ezjail always use my new flavour:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ezjail_default_flavour=&amp;quot;tykbasic&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Configuration =&lt;br /&gt;
This section outlines what I do to further prepare the machine to be a nice ezjail host.&lt;br /&gt;
&lt;br /&gt;
== Firewall ==&lt;br /&gt;
One of the first things I fix is to enable the pf firewall from OpenBSD. I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to enable pf at boot time:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo sysrc pf_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
sudo sysrc pflog_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I also create a very basic &amp;lt;code&amp;gt;/etc/pf.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# cat /etc/pf.conf &lt;br /&gt;
### macros&lt;br /&gt;
if=&amp;quot;em0&amp;quot;&lt;br /&gt;
table &amp;lt;portknock&amp;gt; persist&lt;br /&gt;
&lt;br /&gt;
#external addresses&lt;br /&gt;
tykv4=&amp;quot;a.b.c.d&amp;quot;&lt;br /&gt;
tykv6=&amp;quot;2002:ab:cd::/48&amp;quot;&lt;br /&gt;
table &amp;lt;allowssh&amp;gt; { $tykv4,$tykv6 }&lt;br /&gt;
&lt;br /&gt;
#local addresses&lt;br /&gt;
glasv4=&amp;quot;w.x.y.z&amp;quot;&lt;br /&gt;
&lt;br /&gt;
### scrub&lt;br /&gt;
scrub in on $if all fragment reassemble&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### filtering&lt;br /&gt;
### block everything&lt;br /&gt;
block log all&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### skip loopback interface(s)&lt;br /&gt;
set skip on lo0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### icmp6                                                                                                        &lt;br /&gt;
pass in quick on $if inet6 proto icmp6 all icmp6-type {echoreq,echorep,neighbradv,neighbrsol,routeradv,routersol}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass outgoing&lt;br /&gt;
pass out quick on $if all&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### portknock rule (more than 5 connections in 10 seconds to the port specified will add the &amp;quot;offending&amp;quot; IP to the &amp;lt;portknock&amp;gt; table)&lt;br /&gt;
pass in quick on $if inet proto tcp from any to $glasv4 port 32323 synproxy state (max-src-conn-rate 5/10, overload &amp;lt;portknock&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
### pass incoming ssh and icmp&lt;br /&gt;
pass in quick on $if proto tcp from { &amp;lt;allowssh&amp;gt;, &amp;lt;portknock&amp;gt; } to ($if) port 22&lt;br /&gt;
pass in quick on $if inet proto icmp all icmp-type { 8, 11 }&lt;br /&gt;
&lt;br /&gt;
################&lt;br /&gt;
### pass ipv6 fragments (hack to workaround pf not handling ipv6 fragments)&lt;br /&gt;
pass in on $if inet6&lt;br /&gt;
block in log on $if inet6 proto udp&lt;br /&gt;
block in log on $if inet6 proto tcp&lt;br /&gt;
block in log on $if inet6 proto icmp6&lt;br /&gt;
block in log on $if inet6 proto esp&lt;br /&gt;
block in log on $if inet6 proto ipv6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To load pf without rebooting I run the following:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@ ~]# kldload pf&lt;br /&gt;
[root@ ~]# kldload pflog&lt;br /&gt;
[root@ ~]# pfctl -ef /etc/pf.conf &amp;amp;&amp;amp; sleep 60 &amp;amp;&amp;amp; pfctl -d&lt;br /&gt;
No ALTQ support in kernel&lt;br /&gt;
ALTQ related functions disabled&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I get no prompt after this because pf has cut my SSH connection. But I can SSH back in if I did everything right, and if not, I can just wait 60 seconds after which pf will be disabled again. I SSH in and reattach to the screen I am running this in, and press control-c, so the &amp;quot;sleep 60&amp;quot; is interrupted and pf is not disabled. Neat little trick for when you want to avoid locking yourself out :)&lt;br /&gt;
&lt;br /&gt;
== Replacing sendmail with Postfix ==&lt;br /&gt;
I always replace Sendmail with Postfix on every server I manage. See [[Replacing_Sendmail_With_Postfix]] for more info.&lt;br /&gt;
&lt;br /&gt;
== Listening daemons ==&lt;br /&gt;
When you add an IP alias for a jail, any daemons listening on * will also listen on the jails IP, which is not what I want. For example, I want the jails sshd to be able to listen on the jails IP on port 22, instead of the hosts sshd. Check for listening daemons like so:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1554  12 tcp4   *:25                  *:*&lt;br /&gt;
root     master     1554  13 tcp6   *:25                  *:*&lt;br /&gt;
root     sshd       948   3  tcp6   *:22                  *:*&lt;br /&gt;
root     sshd       948   4  tcp4   *:22                  *:*&lt;br /&gt;
root     syslogd    789   6  udp6   *:514                 *:*&lt;br /&gt;
root     syslogd    789   7  udp4   *:514                 *:*&lt;br /&gt;
[tykling@glas ~]$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This tells me that I need to change Postfix, sshd and syslogd to stop listening on all IP addresses. &lt;br /&gt;
=== Postfix ===&lt;br /&gt;
The defaults in Postfix are really nice on FreeBSD, and most of the time a completely empty config file is fine for a system mailer (sendmail replacement). However, to make Postfix stop listening on port 25 on all IP addresses, I do need one line in &amp;lt;code&amp;gt;/usr/local/etc/postfix/main.cf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/local/etc/postfix/main.cf&lt;br /&gt;
inet_interfaces=localhost&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== sshd ===&lt;br /&gt;
To make &amp;lt;code&amp;gt;sshd&amp;lt;/code&amp;gt; stop listening on all IP addresses I uncomment and edit the &amp;lt;code&amp;gt;ListenAddress&amp;lt;/code&amp;gt; line in &amp;lt;code&amp;gt;/etc/ssh/sshd_config&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep ListenAddress /etc/ssh/sshd_config &lt;br /&gt;
ListenAddress x.y.z.226&lt;br /&gt;
#ListenAddress ::&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
(IP address obfuscated..)&lt;br /&gt;
&lt;br /&gt;
=== syslogd ===&lt;br /&gt;
I don&#039;t need my syslogd to listen on the network at all, so I add the following line to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep syslog /etc/rc.conf &lt;br /&gt;
syslogd_flags=&amp;quot;-ss&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Restarting services ===&lt;br /&gt;
Finally I restart Postfix, sshd and syslogd:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo /etc/rc.d/syslogd restart&lt;br /&gt;
Stopping syslogd.&lt;br /&gt;
Waiting for PIDS: 789.&lt;br /&gt;
Starting syslogd.&lt;br /&gt;
$ sudo /etc/rc.d/sshd restart&lt;br /&gt;
Stopping sshd.&lt;br /&gt;
Waiting for PIDS: 948.&lt;br /&gt;
Starting sshd.&lt;br /&gt;
$ sudo /usr/local/etc/rc.d/postfix restart&lt;br /&gt;
postfix/postfix-script: stopping the Postfix mail system&lt;br /&gt;
postfix/postfix-script: starting the Postfix mail system&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A check with &amp;lt;code&amp;gt;sockstat&amp;lt;/code&amp;gt; reveals that no more services are listening on all IP addresses:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sockstat -l46&lt;br /&gt;
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS      &lt;br /&gt;
root     master     1823  12 tcp4   127.0.0.1:25          *:*&lt;br /&gt;
root     master     1823  13 tcp6   ::1:25                *:*&lt;br /&gt;
root     sshd       1617  3  tcp4   x.y.z.226:22         *:*&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Network configuration ==&lt;br /&gt;
Network configuration is a big part of any jail setup. If I have enough IP addresses (ipv4 and ipv6) I can just add IP aliases as needed. If I only have one or a few v4 IPs I will need to use rfc1918 addresses for the jails. In that case, I create a new loopback interface, &amp;lt;code&amp;gt;lo1&amp;lt;/code&amp;gt; and add the IP aliases there. I then use the pf firewall to redirect incoming traffic to the right jail, depending on the port in use.&lt;br /&gt;
&lt;br /&gt;
=== IPv4 ===&lt;br /&gt;
If rfc1918 jails are needed, I add the following to &amp;lt;code&amp;gt;/etc/rc.conf&amp;lt;/code&amp;gt; to create the lo1 interface on boot:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
### lo1 interface for ipv4 rfc1918 jails&lt;br /&gt;
cloned_interfaces=&amp;quot;lo1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the lo1 interface is created, or if it isn&#039;t needed, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== IPv6 ===&lt;br /&gt;
On the page [[Hetzner_ipv6]] I&#039;ve explained how to make IPv6 work on a Hetzner server where the supplied IPv6 default gateway is outside the IPv6 subnet assigned.&lt;br /&gt;
&lt;br /&gt;
When basic IPv6 connectivity works, I am ready to start adding IP aliases for jails as needed.&lt;br /&gt;
&lt;br /&gt;
=== Allow ping from inside jails ===&lt;br /&gt;
I add the following to &amp;lt;code&amp;gt;/etc/sysctl.conf&amp;lt;/code&amp;gt; so the jails are allowed to do icmp ping. This enables raw socket access, which can be a security issue if you have untrusted root users in your jails. Use with caution.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#allow ping in jails&lt;br /&gt;
security.jail.allow_raw_sockets=1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tips &amp;amp; tricks ==&lt;br /&gt;
=== Get jail info out of &#039;&#039;top&#039;&#039; ===&lt;br /&gt;
To make top show the jail id of the jail in which the process is running in a column, I need to specify the -j flag to top. Since this is a multi-cpu server I am working on, I also like giving the -P flag to top, to get a seperate line of cpu stats per core. Finally, I like -a to get the full commandline/info of the running processes. I add the following to my .bashrc in my homedir on the jail host:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
alias top=&amp;quot;nice top -j -P -a&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...this way I don&#039;t have to remember passing &amp;lt;code&amp;gt;-j -P -a&amp;lt;/code&amp;gt; to top every time. Also, I&#039;ve been told to run top with &#039;&#039;nice&#039;&#039; to limit the cpu used by top itself. I took the advice so the complete alias looks like above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Base jails =&lt;br /&gt;
My jails need various services - they all need firewalling and backup, but some jails also need a database. So I always have a postgres jail on each jail host, so I only need to maintain one postgres server. Also, many of my jails serve some sort of web application, and I like to terminate SSL for those in one place in an attempt to keep the individual jails as simple as possible. This section describes how I configure the postgres and web jails that provide services to the other jails.&lt;br /&gt;
&lt;br /&gt;
== Postgres jail ==&lt;br /&gt;
I don&#039;t need a public v4 IP for this jail, so I configure it with an RFC1918 v4 IP on a loopback interface, and of course a real IPv6 address. I add a AAAA record in DNS for the v6 IP so I have something to point the clients at.&lt;br /&gt;
&lt;br /&gt;
I install the latest Postgres server port, at the time of writing that is &amp;lt;code&amp;gt;databases/postgresql93-server&amp;lt;/code&amp;gt;. But before I can run &amp;lt;code&amp;gt;/usr/local/etc/rc.d/postgresql initdb&amp;lt;/code&amp;gt; I need to permit the use of SysV shared memory in the jail. This is done in the ezjail config file for the jail, in the _parameters line. I need to add &amp;lt;code&amp;gt;allow.sysvipc=1&amp;lt;/code&amp;gt; so I change the line from:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
to:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export jail_postgres_kush_tyknet_dk_parameters=&amp;quot;allow.sysvipc=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
After restarting the jail I can run &amp;lt;code&amp;gt;initdb&amp;lt;/code&amp;gt; and start Postgres. When a jail needs a database I need to:&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a DB user (with the &amp;lt;code&amp;gt;createuser -P someusername&amp;lt;/code&amp;gt; command)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add a database with the new user as owner (&amp;lt;code&amp;gt;createdb -O someusername somedbname&amp;lt;/code&amp;gt;)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Add permissions in &amp;lt;code&amp;gt;/usr/local/pgsql/data/pg_hba.conf&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a hole in the firewall so the jail can reach the database on TCP port &amp;lt;code&amp;gt;5432&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Web Jail ==&lt;br /&gt;
I need a public V4 IP for the web jail and I also give it a V6 IP. Since I use a different V6 IP per website, I will need additional v6 addresses when I start adding websites. I add the v6 addresses to the web jail in batches of 10 as I need them. After creating the jail and bootstrapping the ports collection I install &amp;lt;code&amp;gt;security/openssl&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;www/nginx&amp;lt;/code&amp;gt; and configure it. More on that later.&lt;br /&gt;
&lt;br /&gt;
= ZFS snapshots and backup =&lt;br /&gt;
So, since all this is ZFS based, there is a few tricks I do to make it easier to restore data in case of accidental file deletion or other dataloss.&lt;br /&gt;
&lt;br /&gt;
== Periodic snapshots using sysutils/zfs-periodic ==&lt;br /&gt;
&amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; is a little script that uses the FreeBSD &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt; system to make snapshots of filesystems with regular intervals. It supports making hourly snapshots with a small change to &amp;lt;code&amp;gt;periodic(8)&amp;lt;/code&amp;gt;, but I&#039;ve settled for daily, weekly and monthly snapshots on my servers.&lt;br /&gt;
&lt;br /&gt;
After installing &amp;lt;code&amp;gt;sysutils/zfs-periodic&amp;lt;/code&amp;gt; I add the following to &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs snapshots&lt;br /&gt;
daily_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_snapshot_keep=7&lt;br /&gt;
&lt;br /&gt;
#weekly zfs snapshots&lt;br /&gt;
weekly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
weekly_zfs_snapshot_keep=5&lt;br /&gt;
&lt;br /&gt;
#monthly zfs snapshots&lt;br /&gt;
monthly_zfs_snapshot_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_snapshot_keep=6&lt;br /&gt;
&lt;br /&gt;
#monthly zfs scrub&lt;br /&gt;
monthly_zfs_scrub_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
monthly_zfs_scrub_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that the last bit also enables a monthly scrub of the filesystem. Remember to change the pool name and remember to set the number of snapshots to retain to something appropriate. These things are always a tradeoff between diskspace and safety. Think it over and find some values that make you sleep well at night :)&lt;br /&gt;
&lt;br /&gt;
After this has been running for a few days, you should have a bunch of daily snapshots:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list -t snapshot | grep gelipool@ &lt;br /&gt;
gelipool@daily-2012-09-02                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-03                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-04                                 0      -    31K  -&lt;br /&gt;
gelipool@daily-2012-09-05                                 0      -    31K  -&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Back-to-back ZFS mirroring ==&lt;br /&gt;
I am lucky enough to have more than one of these jail hosts, which is the whole reason I started writing down how I configure them. One of the advantages to having more than one is that I can configure &amp;lt;code&amp;gt;zfs send/receive&amp;lt;/code&amp;gt; jobs and make server A send it&#039;s data to server B, and vice versa.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Introduction ===&lt;br /&gt;
The concept is pretty basic, but as it often happens, security considerations turn what was a simple and elegant idea into something... else. To make the back-to-back backup scheme work without sacrificing too much security, I first make a jail on each jailhost called &amp;lt;code&amp;gt;backup.jailhostname&amp;lt;/code&amp;gt;. This jail will have control over a designated zfs dataset which will house the backups sent from the other server.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Create ZFS dataset ===&lt;br /&gt;
First I create the zfs dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs create cryptopool/backups&lt;br /&gt;
$ sudo zfs set jailed=on cryptopool/backups&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== &#039;jail&#039; the new dataset ===&lt;br /&gt;
I create the jail like I normally do, but after creating it, I edit the ezjail config file and tell it which extra zfs dataset to use:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ grep dataset /usr/local/etc/ezjail/backup_glas_tyknet_dk &lt;br /&gt;
export jail_backup_glas_tyknet_dk_zfs_datasets=&amp;quot;cryptopool/backups&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This makes &amp;lt;code&amp;gt;ezjail&amp;lt;/code&amp;gt; run the &amp;lt;code&amp;gt;zfs jail&amp;lt;/code&amp;gt; command with the proper jail id when the jail is started.&lt;br /&gt;
&lt;br /&gt;
=== jail sysctl settings ===&lt;br /&gt;
I also add the following to the jails ezjail config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# grep parameters /usr/local/etc/ezjail/backup_glas_tyknet_dk&lt;br /&gt;
export jail_backup_glas_tyknet_dk_parameters=&amp;quot;allow.mount.zfs=1 enforce_statfs=1&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuring the backup jail ===&lt;br /&gt;
The jail is ready to run now, and inside the jail a &amp;lt;code&amp;gt;zfs list&amp;lt;/code&amp;gt; looks like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ zfs list&lt;br /&gt;
NAME                                USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool                         3.98G  2.52T    31K  none&lt;br /&gt;
cryptopool/backups                   62K  2.52T    31K  none&lt;br /&gt;
$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I don&#039;t want to open up root ssh access to this jail, but the remote servers need to call &amp;lt;code&amp;gt;zfs receive&amp;lt;/code&amp;gt; which requires root permissions. &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; to the rescue! &amp;lt;code&amp;gt;zfs allow&amp;lt;/code&amp;gt; makes it possible to say &amp;quot;user X is permitted to do action Y on dataset Z&amp;quot; which is what I need here. In the backup jail I add a user called &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; which will be used as the user receiving the zfs snapshots from the remote servers. &lt;br /&gt;
&lt;br /&gt;
I then run the following commands to allow the user to work with the dataset:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo zfs allow tykbackup atime,compression,create,mount,mountpoint,readonly,receive cryptopool/backups&lt;br /&gt;
$ sudo zfs allow cryptopool/backups&lt;br /&gt;
---- Permissions on cryptopool/backups -------------------------------&lt;br /&gt;
Local+Descendent permissions:&lt;br /&gt;
        user tykbackup atime,compression,create,mount,mountpoint,readonly,receive&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Testing if it worked:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ sudo su tykbackup&lt;br /&gt;
$ zfs create cryptopool/backups/test&lt;br /&gt;
$ zfs list cryptopool/backups/test&lt;br /&gt;
NAME                      USED  AVAIL  REFER  MOUNTPOINT&lt;br /&gt;
cryptopool/backups/test    31K  2.52T    31K  none&lt;br /&gt;
$ zfs destroy cryptopool/backups/test&lt;br /&gt;
cannot destroy &#039;cryptopool/backups/test&#039;: permission denied&lt;br /&gt;
$ &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since the user &amp;lt;code&amp;gt;tykbackup&amp;lt;/code&amp;gt; only has the permissions &amp;lt;code&amp;gt;create,mount,mountpoint,receive&amp;lt;/code&amp;gt; on the &amp;lt;code&amp;gt;cryptopool/backups&amp;lt;/code&amp;gt; dataset, I get Permission Denied (as I expected) when trying to destroy &amp;lt;code&amp;gt;cryptopool/backups/test&amp;lt;/code&amp;gt;. Works like a charm.&lt;br /&gt;
&lt;br /&gt;
To allow automatic SSH operations I add the public ssh key for the root user of the server being backed up to &amp;lt;code&amp;gt;/usr/home/tykbackup/.ssh/authorized_keys&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ cat /usr/home/tykbackup/.ssh/authorized_keys&lt;br /&gt;
from=&amp;quot;ryst.tyknet.dk&amp;quot;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command=&amp;quot;/usr/home/tykbackup/zfscmd.sh $SSH_ORIGINAL_COMMAND&amp;quot; ssh-rsa AAAAB3......KR2Z root@ryst.tyknet.dk&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The script called &amp;lt;code&amp;gt;zfscmd.sh&amp;lt;/code&amp;gt; is placed on the backup server to allow the ssh client to issue different command line arguments depending on what needs to be done. The script is very simple:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
shift&lt;br /&gt;
/sbin/zfs $@&lt;br /&gt;
exit $?&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A few notes: Aside from restricting the command this SSH key can run, I&#039;ve restricted it to only be able to log in from the IP of the server being backed up. These are very basic restrictions that should always be in place no matter what kind of backup you are using.&lt;br /&gt;
&lt;br /&gt;
=== Add the periodic script ===&lt;br /&gt;
I then add the script &amp;lt;code&amp;gt;/usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; to each server being backed up with the following content:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/bin/sh&lt;br /&gt;
&lt;br /&gt;
### check pidfile&lt;br /&gt;
if [ -f /var/run/$(basename $0).pid ]; then&lt;br /&gt;
        echo &amp;quot;pidfile /var/run/$(basename $0).pid exists, bailing out&amp;quot;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
echo $$ &amp;gt; /var/run/$(basename $0).pid&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
### If there is a global system configuration file, suck it in.&lt;br /&gt;
if [ -r /etc/defaults/periodic.conf ]; then&lt;br /&gt;
        . /etc/defaults/periodic.conf&lt;br /&gt;
        source_periodic_confs&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
case &amp;quot;$daily_zfs_mirror_enable&amp;quot; in&lt;br /&gt;
    [Yy][Ee][Ss])&lt;br /&gt;
        ;;&lt;br /&gt;
    *)&lt;br /&gt;
        exit&lt;br /&gt;
        ;;&lt;br /&gt;
esac&lt;br /&gt;
&lt;br /&gt;
pools=$daily_zfs_mirror_pools&lt;br /&gt;
if [ -z &amp;quot;$pools&amp;quot; ]; then&lt;br /&gt;
        pools=&#039;tank&#039;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targethost=$daily_zfs_mirror_targethost&lt;br /&gt;
if [ -z &amp;quot;$targethost&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targethost must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetuser=$daily_zfs_mirror_targetuser&lt;br /&gt;
if [ -z &amp;quot;$targetuser&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetuser must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
targetfs=$daily_zfs_mirror_targetfs&lt;br /&gt;
if [ -z &amp;quot;$targetfs&amp;quot; ]; then&lt;br /&gt;
        echo &#039;$daily_zfs_mirror_targetfs must be set in /etc/periodic.conf&#039;&lt;br /&gt;
        exit 1&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
if [ -n &amp;quot;$daily_zfs_mirror_skip&amp;quot; ]; then&lt;br /&gt;
        egrep=&amp;quot;($(echo $daily_zfs_mirror_skip | sed &amp;quot;s/ /|/g&amp;quot;))&amp;quot;&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### get todays date for later use&lt;br /&gt;
tday=$(date +%Y-%m-%d)&lt;br /&gt;
&lt;br /&gt;
echo -n &amp;quot;Doing daily ZFS mirroring - &amp;quot;&lt;br /&gt;
date&lt;br /&gt;
&lt;br /&gt;
### loop through the configured pools&lt;br /&gt;
for pool in $pools; do&lt;br /&gt;
        echo &amp;quot;    Processing pool $pool ...&amp;quot;&lt;br /&gt;
        ### enumerate datasets with daily snapshots from today&lt;br /&gt;
        #echo $egrep&lt;br /&gt;
        #echo &amp;quot;@daily-$tday&amp;quot;&lt;br /&gt;
        if [ -n &amp;quot;$egrep&amp;quot; ]; then&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | egrep -v &amp;quot;$egrep&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
                datasets=$(zfs list -t snapshot -o name | grep &amp;quot;^$pool/&amp;quot; | grep &amp;quot;@daily-$tday&amp;quot;)&lt;br /&gt;
        fi&lt;br /&gt;
&lt;br /&gt;
        echo &amp;quot;found datasets: $datasets&amp;quot;&lt;br /&gt;
&lt;br /&gt;
        for snapshot in $datasets; do&lt;br /&gt;
                dataset=$(echo -n $snapshot | cut -d &amp;quot;@&amp;quot; -f 1 | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                echo &amp;quot;working on dataset $dataset&amp;quot;&lt;br /&gt;
                ### find the latest daily snapshot of this dataset on the remote node, if any&lt;br /&gt;
                echo ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot \| grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; \| cut -d &amp;quot; &amp;quot; -f 1 \| tail -1&lt;br /&gt;
                lastgoodsnap=$(ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh list -t snapshot | grep &amp;quot;^${targetfs}/${dataset}@daily-&amp;quot; | cut -d &amp;quot; &amp;quot; -f 1 | tail -1)&lt;br /&gt;
                if [ -z $lastgoodsnap ]; then&lt;br /&gt;
                        echo &amp;quot;No remote daily snapshot found for local daily snapshot $snapshot - cannot send incremental - sending full backup&amp;quot;&lt;br /&gt;
                        zfs send -v $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -v -F -u -d $targetfs&lt;br /&gt;
                        if [ $? -ne 0 ]; then&lt;br /&gt;
                                echo &amp;quot;    Unable to send full snapshot of $dataset to $targetfs on host $targethost&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                echo &amp;quot;    Successfully sent a full snapshot of $dataset to $targetfs on host $targethost - future sends will be incremental&amp;quot;&lt;br /&gt;
                        fi&lt;br /&gt;
                else&lt;br /&gt;
                        #check if this snapshot has already been sent for some reason, skip if so...&amp;quot;&lt;br /&gt;
                        temp=$(echo $snapshot | cut -d &amp;quot;/&amp;quot; -f 2-)&lt;br /&gt;
                        lastgoodsnap=&amp;quot;$(echo $lastgoodsnap | sed &amp;quot;s,${targetfs}/,,&amp;quot;)&amp;quot;&lt;br /&gt;
                        if [ &amp;quot;$temp&amp;quot; = &amp;quot;$lastgoodsnap&amp;quot; ]; then&lt;br /&gt;
                                echo &amp;quot;    The snapshot $snapshot has already been sent to $targethost, skipping...&amp;quot;&lt;br /&gt;
                        else&lt;br /&gt;
                                ### add pool name to lastgoodsnap&lt;br /&gt;
                                lastgoodsnap=&amp;quot;${pool}/${lastgoodsnap}&amp;quot;&lt;br /&gt;
                                ### zfs send the difference between latest remote snapshot and todays local snapshot&lt;br /&gt;
                                echo &amp;quot;    Sending the diff between local snapshot $(hostname)@$lastgoodsnap and $(hostname)@$snapshot to ${targethost}@${targetfs} ...&amp;quot;&lt;br /&gt;
                                zfs send -I $lastgoodsnap $snapshot | ssh ${targetuser}@${targethost} /usr/home/tykbackup/zfscmd.sh receive -F -u -d $targetfs&lt;br /&gt;
                                if [ $? -ne 0 ]; then&lt;br /&gt;
                                        echo &amp;quot;    There was a problem sending the diff between $lastgoodsnap and $snapshot to $targetfs on $targethost&amp;quot;&lt;br /&gt;
                                else&lt;br /&gt;
                                        echo &amp;quot;    Successfully sent the diff between $lastgoodsnap and $snapshot to $targethost&amp;quot;&lt;br /&gt;
                                fi&lt;br /&gt;
                        fi&lt;br /&gt;
                fi&lt;br /&gt;
        done&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### remove pidfile&lt;br /&gt;
rm /var/run/$(basename $0).pid&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember to also &amp;lt;code&amp;gt;chmod +x /usr/local/etc/periodic/daily/999.zfs-mirror&amp;lt;/code&amp;gt; and enable it in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#daily zfs mirror&lt;br /&gt;
daily_zfs_mirror_enable=&amp;quot;YES&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targethost=&amp;quot;backup.glas.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetuser=&amp;quot;tykbackup&amp;quot;&lt;br /&gt;
daily_zfs_mirror_targetfs=&amp;quot;cryptopool/backups/kush.tyknet.dk&amp;quot;&lt;br /&gt;
daily_zfs_mirror_pools=&amp;quot;tank gelipool&amp;quot;&lt;br /&gt;
daily_zfs_mirror_skip=&amp;quot;gelipool/reserved gelipool/backups&amp;quot;                   # space seperated list of datasets to skip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create the target filesystems in the backup jail ===&lt;br /&gt;
Finally I create the destination filesystems in the backupjail, one per server being backed up. The filesystem that needs to be created is the one specified in the setting &amp;lt;code&amp;gt;daily_zfs_mirror_targetfs&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;/etc/periodic.conf&amp;lt;/code&amp;gt; on the server being backed up.&lt;br /&gt;
&lt;br /&gt;
=== Run the periodic script ===&lt;br /&gt;
I usually to the initial run of the periodic script by hand, so I can catch and fix any errors right away. The script will loop over all datasets in the configured pools and zfs send them including their snapshots to the backup server. Next time the script runs it will send an incremental diff instead of the full dataset.&lt;br /&gt;
&lt;br /&gt;
=== Caveats ===&lt;br /&gt;
This script does not handle deleting datasets (including their snapshots) on the backup server when the dataset is deleted from the server being backed up. You will need to do that manually. This could be considered a feature, or a missing feature, depending on your preferences. :)&lt;br /&gt;
&#039;&#039;&#039;The last bit of these instructions are missing intentionally, will be written asap.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
= Staying up-to-date =&lt;br /&gt;
I update my ezjail hosts and jails to track -STABLE regularly. This section describes the procedure I use. &#039;&#039;&#039;It is essential that the jail host and the jails use the same world and kernel version, or bad stuff will happen.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Updating the jail host ==&lt;br /&gt;
First I update world and kernel of the jail host like I normally would. This is described earlier in this guide, see [[Ezjail_host#Building_world_and_kernel]].&lt;br /&gt;
&lt;br /&gt;
== Updating ezjails basejail ==&lt;br /&gt;
To update ezjails basejail located in &amp;lt;code&amp;gt;/usr/jails/basejail&amp;lt;/code&amp;gt;, I run the same commands as when bootstrapping ezjail, see the section [[Ezjail_host#Bootstrapping_ezjail]].&lt;br /&gt;
&lt;br /&gt;
== Running mergemaster in the jails ==&lt;br /&gt;
Finally, to run mergemaster in all jails I use the following script. It will run mergemaster in each jail, the script comments should explain the rest. When it is finished the jails can be started:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#! /bin/sh&lt;br /&gt;
&lt;br /&gt;
### check if .mergemasterrc exists,&lt;br /&gt;
### move it out of the way if so&lt;br /&gt;
MM_RC=0&lt;br /&gt;
if [ -e /root/.mergemasterrc ]; then&lt;br /&gt;
	MM_RC=1&lt;br /&gt;
	mv /root/.mergemasterrc /root/.mergemasterrc.old&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### loop through jails&lt;br /&gt;
for jailroot in $(ezjail-admin list | cut -c 57- | tail +3 | grep &amp;quot;/&amp;quot;); do&lt;br /&gt;
	echo &amp;quot;processing ${jailroot}:&amp;quot;&lt;br /&gt;
	### check if jailroot exists&lt;br /&gt;
	if [ -n &amp;quot;${jailroot}&amp;quot; -a -d &amp;quot;${jailroot}&amp;quot; ]; then&lt;br /&gt;
		### create .mergemasterrc&lt;br /&gt;
		cat &amp;lt;&amp;lt;EOF &amp;gt; /root/.mergemasterrc&lt;br /&gt;
AUTO_INSTALL=yes&lt;br /&gt;
AUTO_UPGRADE=yes&lt;br /&gt;
FREEBSD_ID=yes&lt;br /&gt;
PRESERVE_FILES=yes&lt;br /&gt;
PRESERVE_FILES_DIR=/var/tmp/mergemaster/preserved-files-$(basename ${jailroot})-$(date +%y%m%d-%H%M%S)&lt;br /&gt;
IGNORE_FILES=&amp;quot;/boot/device.hints /etc/motd&amp;quot;&lt;br /&gt;
EOF&lt;br /&gt;
		### remove backup of /etc from previous run (if it exists)&lt;br /&gt;
		if [ -d &amp;quot;${jailroot}/etc.bak&amp;quot; ]; then&lt;br /&gt;
			rm -rfI &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		fi&lt;br /&gt;
		&lt;br /&gt;
		### create backup of /etc as /etc.bak&lt;br /&gt;
		cp -pRP &amp;quot;${jailroot}/etc&amp;quot; &amp;quot;${jailroot}/etc.bak&amp;quot;&lt;br /&gt;
		&lt;br /&gt;
		### check if mtree from last mergemaster run exists&lt;br /&gt;
		if [ ! -e ${jailroot}/var/db/mergemaster.mtree ]; then&lt;br /&gt;
			### delete /etc/rc.d/*&lt;br /&gt;
			rm -rfI ${jailroot}/etc/rc.d/*&lt;br /&gt;
		fi&lt;br /&gt;
		### run mergemaster for this jail&lt;br /&gt;
		mergemaster -D &amp;quot;${jailroot}&amp;quot;&lt;br /&gt;
	else&lt;br /&gt;
		echo &amp;quot;${jailroot} doesn&#039;t exist&amp;quot;&lt;br /&gt;
	fi&lt;br /&gt;
	sleep 2&lt;br /&gt;
done&lt;br /&gt;
&lt;br /&gt;
### if an existing .mergemasterrc was moved out of the way in the beginning, move it back now&lt;br /&gt;
if [ ${MM_RC} -eq 1 ]; then&lt;br /&gt;
	mv /root/.mergemasterrc.old /root/.mergemasterrc&lt;br /&gt;
else&lt;br /&gt;
	rm /root/.mergemasterrc&lt;br /&gt;
fi&lt;br /&gt;
&lt;br /&gt;
### done, a bit of output&lt;br /&gt;
echo &amp;quot;Done. If everything went well the /etc.bak backup folders can be deleted now.&amp;quot;&lt;br /&gt;
exit 0&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To restart all jails I run the command &amp;lt;code&amp;gt;ezjail-admin restart&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
= Replacing a defective disk =&lt;br /&gt;
I had a broken harddisk on one of my servers this evening. This section describes how I replaced the disk to make everything work again.&lt;br /&gt;
&lt;br /&gt;
== Booting into the rescue system ==&lt;br /&gt;
After Hetzner staff physically replaced the disk my server was unable to boot because the disk that died was the first one on the controller. The cheap Hetzner hardware is unable to boot from the secondary disk, bios restrictions probably. If the other disk had broken the server would have booted fine and this whole process would be done with the server running. Anyway, I booted into the rescue system and partitioned the disk, added a bootloader and added it to the root zpool. After this I was able to boot the server normally, so the rest of the work was done without the rescue system.&lt;br /&gt;
&lt;br /&gt;
== Partitioning the new disk ==&lt;br /&gt;
The following shows the commands I ran to partition the disk:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# gpart create -s GPT /dev/ad4&lt;br /&gt;
ad4 created&lt;br /&gt;
[root@rescue ~]# /sbin/gpart add -b 2048 -t freebsd-boot -s 128 /dev/ad4&lt;br /&gt;
ad4p1 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-zfs -s 30G /dev/ad4&lt;br /&gt;
ad4p2 added&lt;br /&gt;
[root@rescue ~]# gpart add -t freebsd-ufs /dev/ad4&lt;br /&gt;
ad4p3 added&lt;br /&gt;
[root@rescue ~]# gpart show&lt;br /&gt;
=&amp;gt;        34  1465149101  ad6  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
=&amp;gt;        34  1465149101  ad4  GPT  (698G)&lt;br /&gt;
          34        2014       - free -  (1M)&lt;br /&gt;
        2048         128    1  freebsd-boot  (64k)&lt;br /&gt;
        2176    62914560    2  freebsd-zfs  (30G)&lt;br /&gt;
    62916736  1402232399    3  freebsd-ufs  (668G)&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Importing the pool and replacing the disk ==&lt;br /&gt;
&lt;br /&gt;
Next step is importing the zpool (remember altroot=/mnt !) and replacing the defective disk:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[root@rescue ~]# zpool import&lt;br /&gt;
   pool: tank&lt;br /&gt;
     id: 3572845459378280852&lt;br /&gt;
  state: DEGRADED&lt;br /&gt;
 status: One or more devices are missing from the system.&lt;br /&gt;
 action: The pool can be imported despite missing or damaged devices.  The&lt;br /&gt;
        fault tolerance of the pool may be compromised if imported.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
 config:&lt;br /&gt;
&lt;br /&gt;
        tank                      DEGRADED&lt;br /&gt;
          mirror-0                DEGRADED&lt;br /&gt;
            11006001397618753837  UNAVAIL  cannot open&lt;br /&gt;
            ad6p2                 ONLINE&lt;br /&gt;
[root@rescue ~]# zpool import -o altroot=/mnt/ tank&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices could not be opened.  Sufficient replicas exist for&lt;br /&gt;
        the pool to continue functioning in a degraded state.&lt;br /&gt;
action: Attach the missing device and online it using &#039;zpool online&#039;.&lt;br /&gt;
   see: http://www.sun.com/msg/ZFS-8000-2Q&lt;br /&gt;
  scan: scrub repaired 0 in 0h2m with 0 errors on Thu Nov  1 05:00:49 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                      DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
            ad6p2                 ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool replace tank 11006001397618753837 ad4p2&lt;br /&gt;
&lt;br /&gt;
Make sure to wait until resilver is done before rebooting.&lt;br /&gt;
&lt;br /&gt;
If you boot from pool &#039;tank&#039;, you may need to update&lt;br /&gt;
boot code on newly attached disk &#039;ad4p2&#039;.&lt;br /&gt;
&lt;br /&gt;
Assuming you use GPT partitioning and &#039;da0&#039; is your new boot disk&lt;br /&gt;
you may use the following command:&lt;br /&gt;
&lt;br /&gt;
        gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0&lt;br /&gt;
&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:24:41 2012&lt;br /&gt;
        823M scanned out of 3.11G at 45.7M/s, 0h0m to go&lt;br /&gt;
        823M resilvered, 25.88% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank                        DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             UNAVAIL      0     0     0&lt;br /&gt;
              11006001397618753837  UNAVAIL      0     0     0  was /dev/ada0p2&lt;br /&gt;
              ad4p2                 ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ad6p2                   ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]# zpool status&lt;br /&gt;
  pool: tank&lt;br /&gt;
 state: ONLINE&lt;br /&gt;
  scan: resilvered 3.10G in 0h2m with 0 errors on Tue Nov 27 01:26:45 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME        STATE     READ WRITE CKSUM&lt;br /&gt;
        tank        ONLINE       0     0     0&lt;br /&gt;
          mirror-0  ONLINE       0     0     0&lt;br /&gt;
            ada0p2  ONLINE       0     0     0&lt;br /&gt;
            ada1p2  ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[root@rescue ~]#&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reboot into non-rescue system ==&lt;br /&gt;
At this point I rebooted the machine into the normal FreeBSD system.&lt;br /&gt;
&lt;br /&gt;
== Re-create geli partition ==&lt;br /&gt;
To recreate the geli partition on p3 of the new disk, I just follow the same steps as when I originally created it, [http://wiki.tyk.nu/index.php?title=Ezjail_host#Create_GELI_volumes more info here].&lt;br /&gt;
&lt;br /&gt;
To attach the new geli volume I run &amp;lt;code&amp;gt;geli attach&amp;lt;/code&amp;gt; as [http://wiki.tyk.nu/index.php?title=Ezjail_host#Attach_GELI_volumes described here].&lt;br /&gt;
&lt;br /&gt;
== Add the geli device to the encrypted zpool ==&lt;br /&gt;
First I check that both geli devices are available, and I check the device name that needs replacing in &amp;lt;code&amp;gt;zpool status&amp;lt;/code&amp;gt; output:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ geli status&lt;br /&gt;
      Name  Status  Components&lt;br /&gt;
ada1p3.eli  ACTIVE  ada1p3&lt;br /&gt;
ada0p3.eli  ACTIVE  ada0p3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ zpool status gelipool&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices has been removed by the administrator.&lt;br /&gt;
        Sufficient replicas exist for the pool to continue functioning in a&lt;br /&gt;
        degraded state.&lt;br /&gt;
action: Online the device using &#039;zpool online&#039; or replace the device with&lt;br /&gt;
        &#039;zpool replace&#039;.&lt;br /&gt;
  scan: scrub repaired 68K in 0h28m with 0 errors on Thu Nov  1 05:58:08 2012&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                      STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                  DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                DEGRADED     0     0     0&lt;br /&gt;
            18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli&lt;br /&gt;
            ada1p3.eli            ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To replace the device and begin resilvering:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[tykling@haze ~]$ sudo zpool replace gelipool 18431995264718840299 ada0p3.eli&lt;br /&gt;
Password:&lt;br /&gt;
[tykling@haze ~]$ zpool status&lt;br /&gt;
  pool: gelipool&lt;br /&gt;
 state: DEGRADED&lt;br /&gt;
status: One or more devices is currently being resilvered.  The pool will&lt;br /&gt;
        continue to function, possibly in a degraded state.&lt;br /&gt;
action: Wait for the resilver to complete.&lt;br /&gt;
  scan: resilver in progress since Tue Nov 27 00:53:40 2012&lt;br /&gt;
        759M scanned out of 26.9G at 14.6M/s, 0h30m to go&lt;br /&gt;
        759M resilvered, 2.75% done&lt;br /&gt;
config:&lt;br /&gt;
&lt;br /&gt;
        NAME                        STATE     READ WRITE CKSUM&lt;br /&gt;
        gelipool                    DEGRADED     0     0     0&lt;br /&gt;
          mirror-0                  DEGRADED     0     0     0&lt;br /&gt;
            replacing-0             REMOVED      0     0     0&lt;br /&gt;
              18431995264718840299  REMOVED      0     0     0  was /dev/ada0p3.eli/old&lt;br /&gt;
              ada0p3.eli            ONLINE       0     0     0  (resilvering)&lt;br /&gt;
            ada1p3.eli              ONLINE       0     0     0&lt;br /&gt;
&lt;br /&gt;
errors: No known data errors&lt;br /&gt;
[tykling@haze ~]$&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When the resilver is finished, the system is good as new.&lt;/div&gt;</summary>
		<author><name>Tykling</name></author>
	</entry>
</feed>