changeset 465:c8abd9d7aac7

MERGE: rc.d/bhyve from origin into fbhyve
author Franz Glasner <fzglas.hg@dom66.de>
date Fri, 14 Jun 2024 08:30:35 +0200
parents 45d95fee0804 (diff) f1e00e5b2f2d (current diff)
children 4638f2f68b9d
files Makefile files/fbhyve.in usr/local/etc/rc.d/bhyve
diffstat 60 files changed, 6993 insertions(+), 253 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,6 @@
+syntax: regexp
+
+^work/
+^docs/_build/
+^docs/_venv.*/
+^_venv.*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgsigs	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,29 @@
+3c2277cd59064674727d562557f9dd3d766b769b 0 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgtags	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,34 @@
+eda93dcde2e9cff7b999614ac5ea000168d4be37 v0.1
+17b6ee513928cf00d63c6956059bf9def5c45e1e v0.2
+c4ea6a7f68484bcb8a94eec0dd81fa7f6b87ce4b v0.2.1
+0faf9da3718673483cb08ed7dffe3ab6e3c314cf v0.3
+719f8042990a30f20ec99dd2ffe95b1494b16d0a v0.4
+18097b849df368ee511675536b641f26f3d9c4ae v0.5
+edd49e4887dd1db594645fe2661cb515d3863913 v0.6
+78a0f6c19da3c79c7b2bac3911e3c2e0321655a6 v0.7
+8f91de3c121b5db6865c0c381591e7125812e2b3 v0.8
+d6e5c90f50de8b1c553f602b38ba4a807cb5a8bc v0.9
+00a17d4b6dfeff25c3e74501efbeab71274b29a7 v0.10
+41fe82b4a5b096f09fa21572ec562c16428072b8 v0.11
+80b739e9d01f58ce2de8e32ea64e4b978f252a99 v0.12
+daca573155ac1511be07894ddf9fcc78a209eb79 v0.13
+1696158a0628276be0a116467d9ba1c6b836629d v0.13.1
+b729fa681eb56b2e6bbcd5097f2c4083ca52b7a0 v0.14
+0ad969bd3c19b5b895c9d3dd31740d4c869f6a44 v0.15
+acb31b51a897c072d7b62e6da0caa8369e0a3e60 v0.16
+c7a6537995cf9811544047d790c361e2b9526c8f v0.16.1
+16feee945857a8a3ff52222bfd8b9316854512d8 v0.17
+9163fc9c7597585b85c4482ac119dd333a9bb327 v0.18
+a0186e0b980b7ca9c22f18968d1fd478ac2f4ecd v0.18.1
+ccf4c765311223a552ddbb8cef099b6b0efc5646 v0.18.2
+61c6479221fcb8888879e6a8b9805507d706431d v0.19
+88991b4731c95653fb9345da14b6e096983decbe v0.19.1
+dc47c5a49a5c1b6b4c57bce92631fa716fc2cdd0 v0.20
+da77f8d84d711459a392f7a7a602052603c7ed9f v0.21
+73bc78d8cb3f93fd808ee18f2f2605eaa7dd30fe v0.21.1
+d0ee2d9b85137a47b91ce50de14928d5ad4d3819 v0.22
+06fbd3c0f566c2b88305af2a7a11b86270630c24 v0.23
+def510fe4e31ce890ccd04bbf6c083246087de45 v0.24
+baa039348277a49e2858e5ecfda23960050135b9 v0.24.1
+e131280d4b095921cffb20f3ab35d81a8b7a1a19 v0.24.2
+4da3377f4139c17e4ea6e759127adea593dc5913 v0.25
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,117 @@
+# Created by: Franz Glasner <freebsd-dev@dom66.de>
+
+PORTNAME=	local-bsdtools
+PORTVERSION=	0.25
+CATEGORIES=	sysutils
+MASTER_SITES=	# none
+DISTFILES=	# none
+
+MAINTAINER=	freebsd-dev@dom66.de
+COMMENT=	Collection of private system management tools
+
+LICENSE=	BSD3CLAUSE
+
+EXTRACT_DEPENDS=	hg:devel/mercurial
+RUN_DEPENDS=		pkg:ports-mgmt/pkg
+
+NO_ARCH=	yes
+WRKSRC=		${WRKDIR}/local-bsdtools
+
+OPTIONS_DEFINE=	DOCS
+OPTIONS_SUB=	yes
+
+DOCS_BUILD_DEPENDS=	sphinx-build:textproc/py-sphinx
+DOCS_VARS_OFF=		NO_BUILD=yes
+
+USE_RC_SUBR=	fbhyve fwireguard
+
+SUB_LIST=	SIMPLEVERSIONTAG="${SIMPLEVERSIONTAG}"
+
+.include <bsd.port.options.mk>
+
+SRC=		${.CURDIR}
+
+MANPAGES5=	${:!${LS} -1 "${.CURDIR}/docs/man/man5"!}
+MANPAGES8=	${:!${LS} -1 "${.CURDIR}/docs/man/man8"!}
+
+HGCANONICALPATH?=	default
+HGREVISION=	${:!hg id -R "${SRC}" -q!}
+HGDATE=		${:!hg log -R "${SRC}" -r "${HGREVISION:S/+//}" --template '{date|isodatesec}'!}
+HGAUTHOR=	${:!hg log -R "${SRC}" -r "${HGREVISION:S/+//}" --template '{author|person}' | ${TR} ' ' '+'!}
+HGPATH=		${:!hg --config ui.paginate=never path -R "${SRC}" ${HGCANONICALPATH} || echo "file://\$$\(hg root)"!}
+HGPHASE=	${:!hg phase!:[2]}
+SIMPLEVERSIONSTR=	v${PKGVERSION} (rv:${HGREVISION})
+SIMPLEVERSIONTAG=	${PKGORIGIN} v${PKGVERSION} (rv:${HGREVISION})
+VERSIONTAG=	${PKGORIGIN} v${PKGVERSION} (rv:${HGREVISION} with repo at ${HGPATH})
+
+do-extract:
+	${MKDIR} ${WRKSRC}/bin
+	${MKDIR} ${WRKSRC}/sbin
+	${CP} Makefile ${WRKSRC}/Makefile
+.for _rp in sbin/check-ports sbin/fjail sbin/ftjail sbin/fzfs sbin/fpkg sbin/bsmtp2dma
+	${CP} -v ${SRC}/${_rp} ${WRKSRC}/${_rp}
+	${SED} -i "" -e "s|@@VERSION@@|${PORTVERSION}|" ${WRKSRC}/${_rp}
+	${SED} -i "" -e "s|@@ETCDIR@@|${ETCDIR}|" ${WRKSRC}/${_rp}
+	${SED} -i "" -e "s|@@VERSIONTAG@@|${VERSIONTAG}|" ${WRKSRC}/${_rp}
+	${SED} -i "" -e "s|@@SIMPLEVERSIONTAG@@|${SIMPLEVERSIONTAG}|" ${WRKSRC}/${_rp}
+	${SED} -i "" -e "s|@@SIMPLEVERSIONSTR@@|${SIMPLEVERSIONSTR}|" ${WRKSRC}/${_rp}
+.endfor
+	${MKDIR} ${WRKSRC}/etc/periodic/daily
+.for _ef in etc/package-mapping.conf.sample etc/pkgtools.conf.sample etc/bsmtp2dma.conf.sample etc/periodic/daily/800.local-ipv6-refresh etc/periodic/daily/750.local-trim-zfs etc/periodic/daily/720.local-triggered-action
+	${CP} -v ${SRC}/${_ef} ${WRKSRC}/${_ef}
+	${SED} -i "" -e "s|@@SIMPLEVERSIONTAG@@|${SIMPLEVERSIONTAG}|" ${WRKSRC}/${_ef}
+.endfor
+	${MKDIR} ${WRKSRC}/share/${PORTNAME}
+.for _df in share/local-bsdtools/common.subr
+	${CP} -v ${SRC}/${_df} ${WRKSRC}/${_df}
+	${SED} -i "" -e "s|@@SIMPLEVERSIONTAG@@|${SIMPLEVERSIONTAG}|" ${WRKSRC}/${_df}
+.endfor
+	${MKDIR} ${WRKSRC}/share/examples/${PORTNAME}
+.for _sf in share/examples/local-bsdtools/freebsd-update-ftjail-template.sh share/examples/local-bsdtools/freebsd-update-ftjail.sh
+	${CP} -v ${SRC}/${_sf} ${WRKSRC}/${_sf}
+.endfor
+
+post-extract-DOCS-on:
+	${MKDIR} ${WRKSRC}/docs
+	(${TAR} -C ${.CURDIR}/docs -c --exclude ./_build -f - . | ${TAR} -C ${WRKSRC}/docs -x -f - )
+.for _mp in man/man8/local-bsdtools.rst
+	${SED} -i "" -e "s|@@SIMPLEVERSIONTAG@@|${SIMPLEVERSIONTAG}|" ${WRKSRC}/docs/${_mp}
+	${SED} -i "" -e "s|\\\$$HGid\\\$$|\$$HGid: ${HGPATH}/docs/${_mp} ${HGREVISION} ${HGDATE} ${HGAUTHOR} ${HGPHASE} \$$|" ${WRKSRC}/docs/${_mp}
+.endfor
+
+.if ${PORT_OPTIONS:MDOCS}
+do-build:
+	(cd ${WRKSRC}/docs && sphinx-build -M man . _build)
+	(cd ${WRKSRC}/docs && sphinx-build -M html . _build)
+.endif
+
+do-install:
+.for _rp in sbin/check-ports sbin/fjail sbin/ftjail sbin/fzfs sbin/fpkg sbin/bsmtp2dma
+	${INSTALL_SCRIPT} ${WRKSRC}/${_rp} ${STAGEDIR}${PREFIX}/${_rp}
+.endfor
+	${MKDIR} ${STAGEDIR}${ETCDIR}
+.for _ef in package-mapping.conf.sample pkgtools.conf.sample bsmtp2dma.conf.sample
+	${INSTALL_DATA} ${WRKSRC}/etc/${_ef} ${STAGEDIR}${ETCDIR}/${_ef}
+.endfor
+	${MKDIR} ${STAGEDIR}${PREFIX}/etc/periodic/daily
+.for _ps in 800.local-ipv6-refresh 750.local-trim-zfs 720.local-triggered-action
+	${INSTALL_SCRIPT} ${WRKSRC}/etc/periodic/daily/${_ps} ${STAGEDIR}${PREFIX}/etc/periodic/daily
+.endfor
+	${MKDIR} ${STAGEDIR}${DATADIR}
+.for _df in common.subr
+	${INSTALL_DATA} ${WRKSRC}/share/${PORTNAME}/${_df} ${STAGEDIR}${DATADIR}
+.endfor
+	${MKDIR} ${STAGEDIR}${EXAMPLESDIR}
+.for _exf in freebsd-update-ftjail-template.sh freebsd-update-ftjail.sh
+	${INSTALL_DATA} ${WRKSRC}/share/examples/${PORTNAME}/${_exf} ${STAGEDIR}${EXAMPLESDIR}
+.endfor
+
+post-install-DOCS-on:
+.for _mp in ${MANPAGES5:R}
+	${INSTALL_MAN} ${WRKSRC}/docs/_build/man/${_mp}.5 ${STAGEDIR}${PREFIX}/share/man/man5
+.endfor
+.for _mp in ${MANPAGES8:R}
+	${INSTALL_MAN} ${WRKSRC}/docs/_build/man/${_mp}.8 ${STAGEDIR}${PREFIX}/share/man/man8
+.endfor
+
+.include <bsd.port.mk>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Makefile	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/_test_create_thin_jail.sh	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# How to create the basic template datasets for thin jails
+
+_symlink="-P"
+
+FTJAIL="/home/fag/work/ports/sysutils/local-bsdtools/sbin/ftjail"
+
+"$FTJAIL" datasets-tmpl $_symlink zpool/var/tmp/jails/base-ro zpool/var/tmp/jails/skel-rw test1
+
+"$FTJAIL" mount-tmpl $_symlink zpool/var/tmp/jails/base-ro/test1 zpool/var/tmp/jails/skel-rw/test1 /var/tmp/T1
+
+zfs list -r -o name,canmount,atime,sync,exec,setuid,compression,mountpoint zpool/var/tmp/jails
+
+mount
+
+"$FTJAIL" populate-tmpl $_symlink /var/tmp/T1 /root/pkg/base-12.3.txz
+
+if [ "$_symlink" = "-L" ]; then
+    "$FTJAIL" interlink-tmpl /var/tmp/T1
+fi
+
+"$FTJAIL" snapshot-tmpl zpool/var/tmp/jails/base-ro/test1 zpool/var/tmp/jails/skel-rw/test1 12.3-RELEASE
+
+# Create the jail root filesystem by cloning the base RO in read-only mode
+# zfs clone -o readonly=on -o mountpoint=/here/are/my/jails/the-jail -o canmount=noauto|on zpool/var/tmp/jails/base-ro/test1@12.3-RELEASE zpool/var/tmp/jails/the-jail
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/_test_destroy_thin_jail.sh	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+FTJAIL="/home/fag/work/ports/sysutils/local-bsdtools/sbin/ftjail"
+
+"$FTJAIL" umount-tmpl zpool/var/tmp/jails/base-ro/test1 zpool/var/tmp/jails/skel-rw/test1
+
+zfs destroy -rv zpool/var/tmp/jails/skel-rw/test1
+zfs destroy -rv zpool/var/tmp/jails/base-ro/test1
+
+zfs list -r -o name,canmount,atime,sync,exec,setuid,compression,mountpoint zpool/var/tmp/jails
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/conf.py	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,115 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import re
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'local-bsdtools'
+copyright = '2017-2024, Franz Glasner'
+author = 'Franz Glasner'
+
+# The full version, including alpha/beta/rc tags
+with open(os.path.join(os.path.dirname(__file__),
+                       "..",
+                       "Makefile"), "rb") as f:
+    release = re.search(b"^PORTVERSION\s*=\s*(\S+)",
+                        f.read(),
+                        re.MULTILINE).group(1).decode("ascii")
+
+
+# -- General configuration ---------------------------------------------------
+
+master_doc = "index"
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'haiku'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+html_show_sourcelink = False
+
+# For the Haiku title
+html_short_title = "%s %s" % (project, release)
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ("man/man5/bsmtp2dma.conf", "bsmtp2dma.conf", 'Configuration of bsmtp2dma', [author], 5),
+    ("man/man5/package-mapping.conf", "package-mapping.conf", 'Configuration of check-ports', [author], 5),
+    ("man/man5/pkgtools.conf", "pkgtools.conf", 'Configuration for pkgtools and check-ports', [author], 5),
+    ("man/man5/local-bsdtools-periodic", "local-bsdtools-periodic", 'Configuration of periodic scripts installed by local-bsdtools v%s' % release, [author], 5),
+    ("man/man8/local-bsdtools", "local-bsdtools", 'FreeBSD administration helper tools v%s' % release, [author], 8),
+    ("man/man8/bsmtp2dma", "bsmtp2dma", "Bacula compatible mail submission program", [author], 8),
+    ("man/man8/check-ports", "check-ports", "Report the version status of installed packages and check for them also in repositories", [author], 8),
+    ("man/man8/fjail", "fjail", "Management of jails", [author], 8),
+    ("man/man8/fjail-configure", "fjail-configure", "Basic configuration of jails", [author], 8),
+    #("man/man8/fjail-copy", "fjail-copy", "Recursively copy ZFS datasets including all properties", [author], 8),
+    #("man/man8/fjail-datasets", "fjail-datasets", "Create a new tree of ZFS datasets that will encompass a jail", [author], 8),
+    ("man/man8/fjail-freebsd-update", "fjail-freebsd-update", "A checked \"freebsd-update\"", [author], 8),
+    ("man/man8/fjail-hostid", "fjail-hostid", "Generate a proposal for a new BSD Host UUID and ID", [author], 8),
+    #("man/man8/fjail-mount", "fjail-mount", "Recursively mount a ZFS dataset and its children", [author], 8),
+    #("man/man8/fjail-populate", "fjail-populate", "Populate a directory with content from a FreeBSD base.txz", [author], 8),
+    #("man/man8/fjail-privs", "fjail-privs", "Adjust some privileges within a mounted jail", [author], 8),
+    #("man/man8/fjail-umount", "fjail-umount", "Recursively unmount a ZFS datasets and its children", [author], 8),
+    ("man/man8/fpkg", "fpkg", "A frontend for some pkg(8) commands that also operate on running jails", [author], 8),
+    ("man/man8/ftjail", "ftjail", "Management of Thin Jails", [author], 8),
+    ("man/man8/ftjail-build-etcupdate-current-tmpl", "ftjail-build-etcupdate-current-tmpl", "Build a \"current\" tree suitable for the default and extract mode of \"etcupdate\"", [author], 8),
+    ("man/man8/ftjail-copy-skel", "ftjail-copy-skel", "Recursively copy skeleton contents from the template tree into a jail-specific ZFS datasets", [author], 8),
+    ("man/man8/ftjail-datasets-tmpl", "ftjail-datasets-tmpl", "Create ZFS datasets for new Thin Jails using base and skeleton", [author], 8),
+    ("man/man8/ftjail-freebsd-update", "ftjail-freebsd-update", "A freebsd-update implementation for a Thin Jail", [author], 8),
+    ("man/man8/ftjail-interlink-tmpl", "ftjail-interlink-tmpl", "Create proper symlinks for \"skeleton\" style Thin Jails", [author], 8),
+    ("man/man8/ftjail-mount-tmpl", "ftjail-mount-tmpl", "Canonically mount the RO base and the RW skeleton of a Thin Jail", [author], 8),
+("man/man8/ftjail-populate-tmpl", "ftjail-populate-tmpl", "Populate a prepared directory structure with the contents of a FreeBSD base system", [author], 8),
+    ("man/man8/ftjail-snapshot-tmpl", "ftjail-snapshot-tmpl", "Recursively create ZFS snapshots of the RO base datasets and the RW skeleton datasets", [author], 8),
+    ("man/man8/ftjail-umount-tmpl", "ftjail-umount-tmpl", "Unmount mounted Thin Jail template datasets", [author], 8),
+    ("man/man8/fwireguard", "fwireguard", "Manage Wireguard interfaces", [author], 8),
+    ("man/man8/fzfs", "fzfs", "A ZFS management helper tool", [author], 8),
+    ("man/man8/fzfs-copy-tree", "fzfs-copy-tree", "Copy a ZFS dataset tree based on an existing tree", [author], 8),
+    ("man/man8/fzfs-create-tree", "fzfs-create-tree", "Create a ZFS dataset tree structure based on an existing tree", [author], 8),
+    ("man/man8/fzfs-mount", "fzfs-mount", "Recursively mount a ZFS dataset and its children", [author], 8),
+    ("man/man8/fzfs-umount", "fzfs-umount", "Recursively unmount a ZFS datasets and its children", [author], 8),
+]
+
+
+# -- Link to manual pages ----------------------------------------------------
+
+manpages_url = "https://www.freebsd.org/cgi/man.cgi?query={page}&sektion={section}"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/index.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,12 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+.. local-bsdtools documentation master file, created by
+   sphinx-quickstart on Sat Sep 17 21:32:09 2022.
+
+
+Dokumentation
+=============
+
+.. toctree::
+
+   man/index
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/make.bat	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.https://www.sphinx-doc.org/
+	exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/index.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,45 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+Collection of Manual Pages
+==========================
+
+System Administration Tools
+---------------------------
+
+.. toctree::
+
+   man8/local-bsdtools
+   man8/bsmtp2dma
+   man8/check-ports
+   man8/fjail
+   man8/fjail-configure
+   man8/fjail-freebsd-update
+   man8/fjail-hostid
+   man8/fpkg
+   man8/fwireguard
+   man8/ftjail
+   man8/ftjail-build-etcupdate-current-tmpl
+   man8/ftjail-copy-skel
+   man8/ftjail-datasets-tmpl
+   man8/ftjail-freebsd-update
+   man8/ftjail-interlink-tmpl
+   man8/ftjail-mount-tmpl
+   man8/ftjail-populate-tmpl
+   man8/ftjail-snapshot-tmpl
+   man8/ftjail-umount-tmpl
+   man8/fzfs
+   man8/fzfs-copy-tree
+   man8/fzfs-create-tree
+   man8/fzfs-mount
+   man8/fzfs-umount
+
+
+Configuration
+-------------
+
+.. toctree::
+
+   man5/bsmtp2dma.conf
+   man5/local-bsdtools-periodic
+   man5/package-mapping.conf
+   man5/pkgtools.conf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man5/bsmtp2dma.conf.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,25 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+bsmtp2dma.conf
+==============
+
+Description
+-----------
+
+Used by :manpage:`bsmtp2dma(8)`.
+
+It is a shell style configuration file which is sourced in at program start.
+
+The following configuration settings are currently supported:
+
+    ``MAILER``
+
+        The absolute path to the underlying mail command.
+
+        The default is :command:`/usr/sbin/sendmail`.
+
+
+See Also
+--------
+
+:manpage:`bsmtp2dma(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man5/local-bsdtools-periodic.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,167 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+
+local-bsdtools-periodic
+=======================
+
+Synopsis
+--------
+
+**daily/720.local-triggered-action**
+
+**daily/750.local-trim-zfs**
+
+**daily/800.local-ipv6-refresh**
+
+
+Description
+-----------
+
+The package contains a set of periodic scripts.
+
+All scripts are disabled by default.
+
+As all :manpage:`periodic(8)` scripts given scripts can be configured
+in :manpage:`periodic.conf(5)`.
+
+
+Daily Scripts
+~~~~~~~~~~~~~
+
+**720.local-triggered-action**
+
+  Take actions triggered by the existence of given files.
+
+  **daily_local_triggered_action_enable** (bool)
+     Set it to ``YES`` to enable this script.
+
+  **daily_local_triggered_action_files** (str)
+     The readability of any of the given files triggers the action.
+
+  **daily_local_triggered_action_condition** (str)
+     A condition to check before executing the action.
+
+     May be a Shell expression.
+
+     The list of existing trigger files -- which is a subset of
+     `daily_local_triggered_action_files` -- is available in the Shell
+     variable `TRIGGER_FILES`.
+
+  **daily_local_triggered_action_action** (str)
+     The action to execute to.
+
+     May be a Shell expression.
+
+     The list of existing trigger files -- which is a subset of
+     `daily_local_triggered_action_files` -- is available in the Shell
+     variable `TRIGGER_FILES`.
+
+  **daily_local_triggered_action_files_remove** (bool)
+     By default all existing trigger files given in
+     `daily_local_triggered_action_files` are deleted when the action
+     has been executed successfully.
+
+     Set to ``NO`` to disable this behaviour.
+
+  **daily_local_triggered_action_profiles** (str)
+     If a non-empty value is given the script executes in profile-mode.
+
+     If profiles are defined this script is re-executed once for for
+     every profile with the profile as parameter.
+
+     No other global option as `daily_local_triggered_action_enable`
+     is used.
+
+     Instead profile level configurations which are named after the
+     profile are used. The configuration variables have the form
+     `daily_local_triggered_action_${profile}_{files,condition,action,files_remove}`.
+
+     For the sake of variable evaluation some special characters in a given
+     profile are mapped to the underscore ``_``.
+
+**750.local-trim-zfs**
+
+  If enabled it automatically trims ZFS pools at regular invervals by
+  calling :command:`zpool trim`.
+
+  **daily_local_trim_zfs_enable** (bool)
+     Set to ``YES`` to enable this script.
+
+  **daily_local_trim_zfs_pool** (str)
+     The list of ZFS pool to trim to.
+
+     If empty then all known pool are trimmed.
+
+  **daliy_local_trim_zfs_default_threshold** (int)
+     The number of days between trims.
+
+     Default: 35
+
+**800.local-ipv6-refresh**
+
+  On some networks there are issuses of loosing IPv6 connectivity
+  after some uptime with BSD kernels.
+
+  This scripts helps to keep connectivity. It updates the IPv6
+  neighbour cache to ensure proper IPv6 connectivity by calling
+  :command:`traceroute6` at regular intervals.
+
+  **daily_local_ipv6_refresh_enable** (bool)
+     Set to `YES` to enable this script.
+
+  **daily_local_ipv6_refresh_flags** (str)
+     A list of flags given to :manpage:`traceroute(8)`.
+
+     Default: -n -w2 -q2 -m1 -I
+
+  **daily_local_ipv6_refresh_target** (str)
+     The target of :manpage:`traceroute6(8)`.
+
+     If empty then `ipv6_defaultrouter` as given by :manpage:`sysrc(8)`
+     is used -- if available.
+
+
+Examples
+--------
+
+Conditions and actions can be complex Shell expressions. They are evauluated
+with `eval` in the Shell::
+
+    daily_local_triggered_action_condition="service nginx onestatus || service apache2 onestatus"
+
+    daily_local_triggered_action_action="{ service nginx onereload && service apache2 onereload ; } || true"
+
+Profiles::
+
+    daily_local_triggered_action_enable=YES
+
+    daily_local_triggered_action_profiles="p1 p2-1"
+
+    # If the file exists this is executed daily because it is not removed
+    daily_local_triggered_action_p1_files="trigger-for-p1.txt"
+    daily_local_triggered_action_p1_condition="true"
+    daily_local_triggered_action_p1_action="cat \${TRIGGER_FILES} | mail -s ALERT root"
+    daily_local_triggered_action_p1_remove_files="NO"
+
+    daily_local_triggered_action_p2_1_files="trigger-for-p2.txt"
+    daily_local_triggered_action_p2_1_condition="true"
+    daily_local_triggered_action_p2_1_action="echo 'something happened' | mail -s ALERT root"
+
+Note that the use of `TRIGGER_FILES` must be quoted in condition and
+action definitions because they are executed indirectly in the context
+of the Shell's `eval`.
+
+
+See Also
+--------
+
+:manpage:`local-bsdtools(8)`, :manpage:`periodic(8)`,
+:manpage:`periodic.conf(5)`, :manpage:`rc.conf(5)`, :manpage:`sysrc(8)`,
+:manpage:`zpool-trim(8)`, :manpage:`traceroute(8)`
+
+
+Bugs
+----
+
+For a given profile "p1" there is no evaluation of something like
+"daily_local_triggered_action_p1_enable".
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man5/package-mapping.conf.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,43 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+package-mapping.conf
+====================
+
+Description
+-----------
+
+Used by :manpage:`check-ports(8)`.
+
+This is a textfile where every line contains two words -- each being
+a package names::
+
+    package-name  mapped-package-name
+
+When :command:`check-ports` determines the package status for
+`mapped-package-name` then the repositories are checked with regard to
+`package-name` also.
+
+This is useful when the update status of renamed (private) forks should be
+checked against the original parent package also.
+
+
+Examples
+--------
+
+Assume that package ``pack-orig`` is forked into a local package with
+a different name ``my-forked-pack``. To let the :command:`check-ports` tool
+to take this into account use::
+
+    pack-orig my-forked-pack
+
+
+See Also
+--------
+
+:manpage:`check-ports(8)`
+
+
+Bugs
+----
+
+Comments in the file are currently not allowed.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man5/pkgtools.conf.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,15 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+pkgtools.conf
+=============
+
+Description
+-----------
+
+Sourced in by :manpage:`fpkg(8)` and :manpage:`check-ports(8)`.
+
+
+See Also
+--------
+
+:manpage:`check-ports(8)`, :manpage:`fpkg(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/bsmtp2dma.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,102 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+bsmtp2dma
+=========
+
+Synopsis
+--------
+
+**bsmtp2dma** **-V**
+
+**bsmtp2dma** [**-8**] [**-c** `address`] [**-d** `n`] [**-f** `address`] [**-h** `mailhost:port`] [**-l** `number`] [**-r** `address`] [**-s** `subject`] `recipient` ...
+
+
+Description
+-----------
+
+A Bacula compatible mail submission programm and an alternative to Bacula's
+:command:`bsmtp` SMTP client.
+
+Bacula's :command:`bsmtp` needs an underlying mailer that listens on a
+TCP port. :command:`bsmtp2dma` can be used if the underlying mailer does `not`
+listen to an SMTP port (e.g. :manpage:`dma(8)`, :command:`ssmtp` et al.).
+
+The underlying mailer should be compatible to :command:`sendmail`.
+
+
+Options
+-------
+
+.. program:: bsmtp2dma
+
+.. option:: -V
+
+   Show the program version and usage and exit.
+
+.. option:: -8
+
+   Does nothing. Act as a compatibility option for :command:`bsmtp`.
+
+.. option:: -c address
+
+   Set the ``CC:`` header to `address`
+
+.. option:: -d n
+
+   Does nothing. Act as a compatibility option for :command:`bsmtp`.
+
+.. option:: -f address
+
+   Set the ``FROM:`` header to `address`.
+
+   If not given it it set to the sender's address.
+
+.. option:: -h mailhost:port
+
+   Does nothing. Act as a compatibility option for :command:`bsmtp`.
+
+.. option:: -l number
+
+   Does nothing. Act as a compatibility option for :command:`bsmtp`.
+
+.. option:: -r address
+
+   Set the ``Reply-To:`` header to `address`.
+
+.. option:: -s subject
+
+   Set the "Subject:" header to `subject`.
+
+
+Implementation Notes
+--------------------
+
+The body of the email message is read from standard input. Message is
+terminated by sending the ``EOF`` character (:kbd:`Control-d` on many
+systems) on the start of a new line, much like many :command:`mail`
+commands.
+
+The message is collected into a temporary file and send to all given
+recipients by using the configured underlying mail command.
+
+
+Files
+-----
+
+:command:`bsmtp2dma` reads a configuration file -- if available -- from
+:file:`/usr/local/etc/local-bsdtools/bsmtp2dma.conf`.
+
+It is a shell style configuration file which is sourced in at program start.
+
+The following configuration settings are currently supported:
+
+  ``MAILER``
+      The absolute path to the underlying mail command.
+
+      The default is :command:`/usr/sbin/sendmail`.
+
+
+See Also
+--------
+
+:manpage:`bsmtp2dma.conf(5)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/check-ports.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,234 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+check-ports
+===========
+
+Synopsis
+--------
+
+**check-ports -hV**
+
+**check-ports** **-n** `package` ...
+
+
+Description
+-----------
+
+Report and check the version status of installed packages and compare
+them to version in remote repositories and the local ports index.
+
+By default (without any option) the status of every package is
+printed with respect to repositories that have the package and have
+differing versions. This includes the ports INDEX.
+
+.. program:: check-ports
+
+.. option:: -h
+
+   Print a short usage message to stdout and exit.
+
+.. option:: -V
+
+   Print the program name and version number to stdout and exit.
+
+.. option:: -A
+
+   Print for every package the status of all repositories.
+
+.. option:: -a
+
+   Print the data of all repos that have the package.
+
+.. option:: -n
+
+   Print the status of given packages in `package` in all details.
+   No other options are respected.
+
+.. option:: -s
+
+   Print the status of all packages that need some attention;
+   version differences with regard to the ports INDEX only are to be ignored.
+
+.. option:: -v
+
+   Print the title and repository of every installed package always.
+
+
+Environment
+-----------
+
+.. envvar:: INDEXDIR
+
+   If set, the directory to search for `INDEXFILE`. If unset,
+   :envvar:`PORTSDIR` will be used instead.
+
+.. envvar:: INDEXFILE
+
+   The filename of the ports index, search for in :envvar:`INDEXDIR` or
+   :envvar:`PORTSDIR`.
+   Default: `INDEX-N` where `N` is the OS major version number.
+
+.. envvar:: PORTSDIR
+
+   Specifies the location to the Ports directory.
+   Default: :file:`/usr/ports`.
+
+
+Files
+-----
+
+:file:`/usr/local/etc/local-bsdtools/package-mapping.conf`
+
+:file:`/usr/local/etc/local-bsdtools/pkgtools.conf`
+
+
+Examples
+--------
+
+Report the status of all installed packages with respect to all configured
+repositories and the ports index (if available)::
+
+   # check-ports -A
+   tdb                                  1.4.3,1           (FreeBSD)
+     INDEX          : 1.4.7,1           < needs updating (index has 1.4.7,1)
+     FreeBSD        : 1.4.7,1           < needs updating (remote has 1.4.7,1)
+     LocalBSDPorts  :                   ?
+     SharedLocalRepo:                   ?
+     LocalRepo      :                   ?
+   teckit                               2.5.11            (FreeBSD)
+     INDEX          : 2.5.11            = up-to-date with index
+     FreeBSD        : 2.5.11            = up-to-date with remote
+     LocalBSDPorts  :                   ?
+     SharedLocalRepo:                   ?
+     LocalRepo      :                   ?
+   tevent                               0.10.2_1          (FreeBSD)
+     INDEX          : 0.13.0_1          < needs updating (index has 0.13.0_1)
+     FreeBSD        : 0.13.0            < needs updating (remote has 0.13.0)
+     LocalBSDPorts  :                   ?
+     SharedLocalRepo:                   ?
+     LocalRepo      :                   ?
+   tex-basic-engines                    20210325          (FreeBSD)
+     INDEX          : 20210325          = up-to-date with index
+     FreeBSD        : 20210325          = up-to-date with remote
+     LocalBSDPorts  :                   ?
+     SharedLocalRepo:                   ?
+     LocalRepo      :                   ?
+   #
+
+Report the status of all installed packages with respect to all configured
+repositories that provide the package::
+
+   # check-ports -a
+   tdb                                  1.4.3,1           (FreeBSD)
+     INDEX          : 1.4.7,1           < needs updating (index has 1.4.7,1)
+     FreeBSD        : 1.4.7,1           < needs updating (remote has 1.4.7,1)
+   teckit                               2.5.11            (FreeBSD)
+     INDEX          : 2.5.11            = up-to-date with index
+     FreeBSD        : 2.5.11            = up-to-date with remote
+   tevent                               0.10.2_1          (FreeBSD)
+     INDEX          : 0.13.0_1          < needs updating (index has 0.13.0_1)
+     FreeBSD        : 0.13.0            < needs updating (remote has 0.13.0)
+   tex-basic-engines                    20210325          (FreeBSD)
+     INDEX          : 20210325          = up-to-date with index
+     FreeBSD        : 20210325          = up-to-date with remote
+   #
+
+The standard output considers installed packages with versions that differ in any of
+the configured repositories *including*  a ports INDEX::
+
+   # check-ports
+   tdb                                  1.4.3,1           (FreeBSD)
+     INDEX          : 1.4.7,1           < needs updating (index has 1.4.7,1)
+     FreeBSD        : 1.4.7,1           < needs updating (remote has 1.4.7,1)
+   tevent                               0.10.2_1          (FreeBSD)
+     INDEX          : 0.13.0_1          < needs updating (index has 0.13.0_1)
+     FreeBSD        : 0.13.0            < needs updating (remote has 0.13.0)
+   tex-xetex                            0.99993_1         (FreeBSD)
+     INDEX          : 0.99993_2         < needs updating (index has 0.99993_2)
+     FreeBSD        : 0.99993_1         = up-to-date with remote
+   texlive-base                         20210325_5        (FreeBSD)
+     INDEX          : 20210325_10       < needs updating (index has 20210325_10)
+     FreeBSD        : 20210325_8        < needs updating (remote has 20210325_8)
+   #
+
+The effect of an additional :option:`-v` on :command:`check-ports` is::
+
+   # check-ports -v
+   tdb                                  1.4.3,1           (FreeBSD)
+     INDEX          : 1.4.7,1           < needs updating (index has 1.4.7,1)
+     FreeBSD        : 1.4.7,1           < needs updating (remote has 1.4.7,1)
+   teckit                               2.5.11            (FreeBSD)
+   tevent                               0.10.2_1          (FreeBSD)
+     INDEX          : 0.13.0_1          < needs updating (index has 0.13.0_1)
+     FreeBSD        : 0.13.0            < needs updating (remote has 0.13.0)
+   tex-basic-engines                    20210325          (FreeBSD)
+   tex-dvipdfmx                         20210325          (FreeBSD)
+   tex-dvipsk                           2021.1            (FreeBSD)
+   tex-formats                          20210325_1        (FreeBSD)
+   tex-jadetex                          3.13_4            (FreeBSD)
+   tex-kpathsea                         6.3.3             (FreeBSD)
+   tex-libtexlua                        5.3.6             (FreeBSD)
+   tex-libtexluajit                     2.1.0             (FreeBSD)
+   tex-luatex                           1.12.0            (FreeBSD)
+   tex-ptexenc                          1.3.9             (FreeBSD)
+   tex-synctex                          2.0.0_1           (FreeBSD)
+   tex-web2c                            20210325          (FreeBSD)
+   tex-xdvik                            22.87.06          (FreeBSD)
+   tex-xetex                            0.99993_1         (FreeBSD)
+     INDEX          : 0.99993_2         < needs updating (index has 0.99993_2)
+     FreeBSD        : 0.99993_1         = up-to-date with remote
+   tex-xmltex                           1.9_3             (FreeBSD)
+   texlive-base                         20210325_5        (FreeBSD)
+     INDEX          : 20210325_10       < needs updating (index has 20210325_10)
+     FreeBSD        : 20210325_8        < needs updating (remote has 20210325_8)
+   #
+
+The :option:`-s` suppresses the output if only the version of a ports INDEX differs::
+
+   # check-ports -s
+   tdb                                  1.4.3,1           (FreeBSD)
+     INDEX          : 1.4.7,1           < needs updating (index has 1.4.7,1)
+     FreeBSD        : 1.4.7,1           < needs updating (remote has 1.4.7,1)
+   tevent                               0.10.2_1          (FreeBSD)
+     INDEX          : 0.13.0_1          < needs updating (index has 0.13.0_1)
+     FreeBSD        : 0.13.0            < needs updating (remote has 0.13.0)
+   texlive-base                         20210325_5        (FreeBSD)
+     INDEX          : 20210325_10       < needs updating (index has 20210325_10)
+     FreeBSD        : 20210325_8        < needs updating (remote has 20210325_8)
+   #
+
+The effect of an additional :option:`-v` on :command:`checkports -s` is::
+
+   # check-ports -sv
+   tdb                                  1.4.3,1           (FreeBSD)
+     INDEX          : 1.4.7,1           < needs updating (index has 1.4.7,1)
+     FreeBSD        : 1.4.7,1           < needs updating (remote has 1.4.7,1)
+   teckit                               2.5.11            (FreeBSD)
+   tevent                               0.10.2_1          (FreeBSD)
+     INDEX          : 0.13.0_1          < needs updating (index has 0.13.0_1)
+     FreeBSD        : 0.13.0            < needs updating (remote has 0.13.0)
+   tex-basic-engines                    20210325          (FreeBSD)
+   tex-dvipdfmx                         20210325          (FreeBSD)
+   tex-dvipsk                           2021.1            (FreeBSD)
+   tex-formats                          20210325_1        (FreeBSD)
+   tex-jadetex                          3.13_4            (FreeBSD)
+   tex-kpathsea                         6.3.3             (FreeBSD)
+   tex-libtexlua                        5.3.6             (FreeBSD)
+   tex-libtexluajit                     2.1.0             (FreeBSD)
+   tex-luatex                           1.12.0            (FreeBSD)
+   tex-ptexenc                          1.3.9             (FreeBSD)
+   tex-synctex                          2.0.0_1           (FreeBSD)
+   tex-web2c                            20210325          (FreeBSD)
+   tex-xdvik                            22.87.06          (FreeBSD)
+   tex-xetex                            0.99993_1         (FreeBSD)
+   tex-xmltex                           1.9_3             (FreeBSD)
+   texlive-base                         20210325_5        (FreeBSD)
+     INDEX          : 20210325_10       < needs updating (index has 20210325_10)
+     FreeBSD        : 20210325_8        < needs updating (remote has 20210325_8)
+   #
+
+
+See Also
+--------
+
+:manpage:`fpkg(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fjail-configure.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,65 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fjail-configure
+===============
+
+Synopsis
+--------
+
+**fjail configure** [**-d**] `mountpoint`
+
+
+Description
+-----------
+
+Configure some basic settings of a jail that is mounted at `mountpoint`.
+
+.. program:: fjail configure
+
+.. option:: -d
+
+   Temporarily also mount a standard devfs filesystem at `mountpoint`/dev.
+
+The following configuration settings are applied:
+
+  The "root" account within the jail is deactivated.
+
+  In the jail's :file:`/etc/rc.conf`::
+
+    sendmail_enable="NONE"
+    clear_tmp_enable="YES"
+    clear_tmp_X="NO"
+    syslogd_flags="-ss"
+    bsdstats_enable="NO"
+
+  The timezone is set to "Europe/Berlin" if not yet set.
+
+  The :file:`/etc/resolv.conf` is copied from the host into the jail if
+  the target does not exist yet.
+
+  :command:`/usr/bin/newaliases` is called within the jail.
+
+  In the jail's :file:`/etc/periodic.conf.local`::
+
+    daily_ntpd_leapfile_enable="NO"
+    daily_status_zfs_zpool_list_enable="NO"
+    daily_status_disks_enable="NO"
+    daily_status_uptime_enable="NO"
+
+This command can be used for all sort of jails (normal, thin).
+
+A proposal for a hostid suitable for use within the jail is printed to
+stdout also; this is done by calling :command:`fjail hostid`.
+
+
+Implementation Notes
+--------------------
+
+A populated and working dev filesystem within the jail is needed to
+work properly. This is checked for.
+
+
+See Also
+--------
+
+:manpage:`fjail(8)`, :manpage:`ftjail(8)`, :manpage:`fjail-hostid(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fjail-freebsd-update.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,41 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fjail-freebsd-update
+====================
+
+Synopsis
+--------
+
+**fjail freebsd-update** [**-c** `currently-running`] `directory`
+
+
+Description
+-----------
+
+A checked :manpage:`freebsd-update(8)` for a system located or mounted
+at `directory`. A corresponding jail may or may not be running.
+
+.. program:: fjail freebsd-update
+
+.. option:: -c currently-running
+
+   Do not assume that the system at `directory` is currently running
+   the host's FreeBSD version but the version given in
+   `currently-running`.
+
+   This will also be checked by :manpage:`freebsd-version(1)` for
+   `directory`.
+
+
+Environment
+-----------
+
+All environment variables that affect :manpage:`freebsd-update` are
+effective also.
+
+
+See Also
+--------
+
+:manpage:`fjail(8)`, :manpage:`freebsd-update(8)`,
+:manpage:`freebsd-version(1)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fjail-hostid.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,35 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fjail-hostid
+============
+
+Synopsis
+--------
+
+**fjail hostid**
+
+
+Description
+-----------
+
+Generate a proposal for a new BSD host UUID and ID and print it to stdout.
+
+The output is suitable for inclusion in a :file:`/etc/jail.conf`.
+
+
+Examples
+--------
+
+::
+
+   # fjail hostid
+   Proposed hostuuid/hostid:
+     host.hostuuid = "868be13d-6fc0-11ed-827f-74d435fd3892";
+     host.hostid = 582606249;
+   #
+
+
+See Also
+--------
+
+:manpage:`fjail(8)`, :manpage:`ftjail(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fjail.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,92 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail
+======
+
+Synopsis
+--------
+
+**fjail -hV**
+
+**fjail subcommand**
+
+
+Description
+-----------
+
+Management tool for Jails: creation of ZFS dataset hierarchies, mount,
+population and configuration helpers.
+
+The following global options are implemented:
+
+.. program:: fjail
+
+.. option:: -h
+
+   Print a short usage message to stdout and exit.
+
+.. option:: -V
+
+   Print the program name and version number to stdout and exit.
+
+
+Subcommands
+-----------
+
+:manpage:`fjail-configure(8)`
+
+    Do some basic configuration of an already populated and mounted
+    jail
+
+:manpage:`fjail-copy(8)`
+
+    Recursively copy ZFS datasets including all properties
+
+:manpage:`fjail-datasets(8)`
+
+    Create a new tree of ZFS datasets that will encompass a jail
+
+:manpage:`fjail-freebsd-update(8)`
+
+    Do a :manpage:`freebsd-update(8)` with some additional
+    compatibility checks
+
+:manpage:`fjail-hostid(8)`
+
+    Generate a proposal for a new BSD host UUID and ID
+
+:manpage:`fjail-mount(8)`
+
+    Recursively mount a ZFS dataset and its children.
+
+    It is just an alias for :command:`fzfs mount`
+    (see :manpage:`fzfs-mount(8)`).
+
+:manpage:`fjail-populate(8)`
+
+    Populate a directory with content from a FreeBSD base.txz
+
+:manpage:`fjail-privs(8)`
+
+    Adjust some privileges within a mounted jail
+
+:manpage:`fjail-umount(8)`
+
+    Recursively unmount a ZFS datasets and its children.
+
+    It is just an alias for :command:`fzfs umount`
+    (see :manpage:`fzfs-umount(8)`).
+
+
+Implementation Notes
+--------------------
+
+Some commands require ZFS as filesystem.
+
+Some commands are suitable for Thin Jails also.
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fpkg.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,136 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fpkg
+====
+
+Synopsis
+--------
+
+**fpkg -hV**
+
+**fpkg subcommand**
+
+
+Description
+-----------
+
+A :manpage:`pkg(8)` frontend for common operations that also operates
+in all running and compatible jails.
+
+A "compatible jail" is a jail that's output of
+:command:`freebsd-version -u` is the same as the host's.
+
+The following global options are implemented:
+
+.. program:: fpkg
+
+.. option:: -h
+
+   Print a short usage message to stdout and exit.
+
+.. option:: -V
+
+   Print the program name and version number to stdout and exit.
+
+
+Subcommands
+-----------
+
+:manpage:`fpkg-audit(8)`
+
+    Call :command:`pkg audit` on the local host and all running
+    visible and compatible jails.
+
+:manpage:`fpkg-check-fast-track(8)`
+
+    Check packages installed from the LocalBSDPorts repository against
+    the repositories `FreeBSD` and `LocalBSDPorts` on the local host
+    and all visible and compatible jails
+
+:manpage:`fpkg-check-upgrade(8)`
+
+    Call :command:`pkg upgrade -n` on the local host and all running
+    visible and compatible jails.
+
+:manpage:`fpkg-config(8)`
+
+    Retrieve the value of a given :manpage:`pkg(8)` configuration
+    option on the local host and all running visible and
+    compatible jails (:command:`pkg config`).
+
+:manpage:`fpkg-update(8)`
+
+    Call :command:`pkg update` on the local host and all running
+    visible and compatible jails.
+
+:manpage:`fpkg-upgrade(8)`
+
+    Call :command:`pkg upgrade` on the local host and all running
+    visible and compatible jails.
+
+:manpage:`fpkg-uversion(8)`
+
+    Call :command:`freebsd-version -u` on the local host and all
+    running visible and compatible jails.
+
+:manpage:`fpkg-vv(8)`
+
+    Call :command:`pkg -vv` on the local host and all running visible
+    and compatible jails.
+
+
+Environment
+-----------
+
+.. envvar:: FPKG_AUDIT_FLAGS
+
+   Additional flags given to :command:`pkg audit` (Default: ``-Fr``).
+
+.. envvar:: FPKG_UPDATE_FLAGS
+
+   Additional flags given to :command:`pkg update` (Default: empty).
+
+.. envvar:: FPKG_UPGRADE_FLAGS
+
+   Additional flags given to :command:`pkg upgrade` and
+   :command:`pkg upgrade -n` (Default: empty).
+
+.. envvar:: FPKG_SIGN
+
+   Marker for the begin of an output group (local host or jail)
+   (Default: "===> ").
+
+.. envvar:: FPKG_SKIPSIGN
+
+   Marker for the begin of a skipped output group (Default: "----> ").
+
+.. envvar:: FREEBSD_REPO
+
+   The name of the (official) FreeBSD package repository (Default:
+   ``FreeBSD``).
+
+.. envvar:: LOCALBSDPORTS_REPO
+
+   Repository with ports with default port OPTIONS (i.e. unchanged)
+   but with newer package versions as the "FreeBSD" repository. Some sort
+   of fast-track repository.
+
+All other environment variables that affect :manpage:`pkg(8)` are
+effective also.
+
+
+Files
+-----
+
+If there is a :file:`/usr/local/etc/local-bsdtools/pkgtools.conf` then
+this file is sourced in. All environment variables that are documented
+in `Environment`_ can be set in this file also.
+
+This configuration file is a Bourne shell (:command:`/bin/sh`)
+compatible file.
+
+
+See Also
+--------
+
+:manpage:`check-ports(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail-build-etcupdate-current-tmpl.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,39 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail-build-etcupdate-current-tmpl
+===================================
+
+Synopsis
+--------
+
+**ftjail build-etcupdate-current-tmpl** `directory` `tarball`
+
+
+Description
+-----------
+
+Build a tarball in `tarball` of a "current" tree from the template
+mounted at `directory`.
+
+This tarball can be used by the default and extract modes of
+:command:`etcupdate`. Using a tarball can allow :command:`etcupdate`
+to perform a merge without requiring a source tree that matches the
+currently installed world.  The tarball argument specifies the name of
+the file to create.  The file will be a :manpage:`tar(5)` file compressed with
+:manpage:`bzip2(1)`.
+
+Within `directory` the default database locations are assumed. They are
+:file:`{directory}/var/db/etcupdate/current`.
+
+
+Environment
+-----------
+
+All environment variables that affect :manpage:`tar(1)` are effective also.
+
+
+See Also
+--------
+
+:manpage:`ftjail(8)`, :manpage:`etcupdate(8)`,
+:manpage:`tar(1)`, :manpage:`tar(5)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail-copy-skel.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,66 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail-copy-skel
+================
+
+Synopsis
+--------
+
+**ftjail copy-skel** [**-A**] [**-L**] [**-M** `mountpoint`] [**-P**] [**-u**] `source-dataset` `snapshot-name` `target-dataset`
+
+
+Description
+-----------
+
+Recursively copy the snapshot `snapshot-name` of the RW skeleton dataset
+`source-dataset` into dataset `target-dataset`.
+
+Apply knowledge of the type and typical layout of the RW skeleton
+(e.g. handle the intermediate dataset of :file:`/usr` properly).
+
+
+Options
+-------
+
+.. program:: ftjail copy-skel
+
+.. option:: -A
+
+   Set the ZFS property `canmount=noauto` for all datasets in the
+   target dataset.
+
+.. option:: -D
+
+   Just copy the dataset tree including properties but no filesystem
+   data. This option is incompatible to option :option:`-L`.
+
+.. option:: -L
+
+   Copy dataset properties optimized for employing a :file:`skeleton`
+   subdirectory.
+
+.. option:: -M mountpoint
+
+   Set the `mountpoint` property for the TARGET-DS to `mountpoint`.
+   All children will be set to inherit it.
+
+.. option:: -P
+
+   Copy dataset properties optimized for direct mounts of skeleton
+   children over an already mounted base.
+
+.. option:: -u
+
+   Do not mount the target dataset `target-dataset` automatically.
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.
+
+
+See Also
+--------
+
+:manpage:`ftjail(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail-datasets-tmpl.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,53 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail-datasets-tmpl
+====================
+
+Synopsis
+--------
+
+**ftjail datasets-tmpl** [**-L**] `dataset-parent-base` `dataset-parent-skeleton` `name`
+
+**ftjail datasets-tmpl** [**-P**]  `dataset-parent-base` `dataset-parent-skeleton` `name`
+
+
+Description
+-----------
+
+Create ZFS template datasets, i.e. the ro base and the rw skeleton to
+be used within Thin Jails.
+
+The ZFS datasets `dataset-parent-base` and `dataset-parent-skeleton`
+must exist already.
+
+A child with name `name` must not exist already for both given parent
+datasets.
+
+
+Options
+-------
+
+.. program:: ftjail datasets-tmpl
+
+.. option:: -L
+
+   Create dataset properties optimized for employing a
+   :file:`skeleton` subdirectory (i.e. Thin Jails using symbolic links
+   from a (read-only) root into the writeable parts of the system).
+
+.. option:: -P
+
+   Create ZFS dataset properties optimized for direct mounts of skeleton
+   children over an already mounted base.
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.
+
+
+See Also
+--------
+
+:manpage:`ftjail(8)`, :manpage:`ftjail-mount-tmpl(8)`,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail-freebsd-update.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,60 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail-freebsd-update
+=====================
+
+Synopsis
+--------
+
+**ftjail freebsd-update** [**-k**] [**-o** `old-origin`] `directory` `new-origin` [`etcupdate-tarball`]
+
+
+Description
+-----------
+
+A :manpage:`freebsd-update(8)` for a Thin Jail.
+
+Make the ZFS dataset mounted at `directory` a read-only clone of `new-origin`.
+
+If `etcupdate-tarball` is given also call :manpage:`etcupdate(8)` on
+the fully re-mounted directory tree that is rooted at `directory`
+using `etcupdate-tarball` as the new "current".
+
+
+Options
+-------
+
+.. program:: ftjail freebsd-update
+
+.. option:: -k
+
+   Keep all temporary files.
+
+   .. note:: On unexpected errors temp files are automatically kept.
+
+.. option:: -o old-origin
+
+   In addition to check that `directory` is a ZFS clone also check that
+   its origin is equal to `old-origin`.
+
+   Note that a check that `directory` is a ZFS clone with some origin
+   is done by default.
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.
+
+
+Files
+-----
+
+A unique temporary directory is created within :file:`/var/tmp`. All
+temporary files are created within this directory.
+
+
+See Also
+--------
+
+:manpage:`ftjail(8)`, :manpage:`freebsd-update(8)`, :manpage:`etcupdate(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail-interlink-tmpl.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,27 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail-interlink-tmpl
+=====================
+
+Synopsis
+--------
+
+**ftjail interlink-tmpl** `directory`
+
+
+Description
+-----------
+
+Create symbolic links between the RO base and the RW skeleton in
+:file:`skeleton`.
+
+Base and skeleton must be canonically mounted already into
+:file:`directory` (e.g. using :manpage:`ftjail-mount-tmpl(8)`).
+
+Only applicable to Thin Jails with the :file:`skeleton` symlink layout.
+
+
+See Also
+--------
+
+:manpage:`ftjail(8)`, :manpage:`ftjail-mount-tmpl(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail-mount-tmpl.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,57 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail-mount-tmpl
+=================
+
+Synopsis
+--------
+
+**ftjail mount-tmpl** [**-L**] [**-n**] [**-u**] `dataset-base-ro` `dataset-skeleton-rw`  `mountpoint`
+
+**ftjail mount-tmpl** [**-P**] [**-n**] [**-u**] `dataset-base-ro` `dataset-skeleton-rw`  `mountpoint`
+
+
+Description
+-----------
+
+Canonically mount a Thin Jail template with its base in `dataset-base-ro`
+and its skeleton in `dataset-skeleton-rw` at mountpoint `mountpoint`.
+
+All children that can be mounted will also mounted properly.
+
+
+Options
+-------
+
+.. program:: ftjail mount-tmpl
+
+.. option:: -L
+
+   Mount `dataset-skeleton-rw` into a :file:`skeleton` subdirectory.
+
+.. option:: -P
+
+   Recursively mount only the children of the skeleton dataset
+   `dataset-skeleton-rw` directly over the base -- skipping
+   just the parent dataset in `dataset-skeleton-rw`.
+
+.. option:: -n
+
+   Do not really mount anything but show what would be mounted where.
+
+.. option:: -u
+
+   Alias of :option:`-n`
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.   
+
+
+See Also
+--------
+
+:manpage:`ftjail(8)`, :manpage:`ftjail-umount-tmpl(8)`,
+:manpage:`ftjail-datasets-tmpl(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail-populate-tmpl.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,54 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail-populate-tmpl
+====================
+
+Synopsis
+--------
+
+**ftjail populate-tmpl** [**-b**] **-L** `directory` `path/to/base.txz` [`path/to/kernel.txz`]
+
+**ftjail populate-tmpl** [**-b**] **-P** `directory` `path/to/base.txz` [`path/to/kernel.txz`]
+
+
+Description
+-----------
+
+Populate the directory in `directory` with a FreeBSD base system.
+The base system's archive ist in `path/to/base.txz`.
+
+Optionally a FreeBSD kernel can be extracted also.
+
+The directory in `directory` must already have a proper
+directory layout.
+This can be accomplished by :manpage:`ftjail-mount-tmpl(8)`.
+
+Apply knowledge of the type and typical layout of the RW skeleton
+(e.g. handle the :file:`skeleton` symlink properly).
+
+
+Options
+-------
+
+.. program:: ftjail populate-tmpl
+
+.. option:: -L
+
+   Copy dataset properties optimized for employing a :file:`skeleton`
+   subdirectory.
+
+.. option:: -P
+
+   Copy dataset properties optimized for direct mounts of skeleton
+   children over an already mounted base.
+
+.. option:: -b
+
+   Do not empty the ``/boot`` folder in `directory` but preserve it.
+   This option is implied if a `path/to/kernel.txz` is given.
+
+
+See Also
+--------
+
+:manpage:`ftjail(8)`, :manpage:`ftjail-mount-tmpl(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail-snapshot-tmpl.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,29 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail-snapshot-tmpl
+====================
+
+Synopsis
+--------
+
+**ftjail snapshot-tmpl** `dataset-ro-base` `dataset-skeleton-rw` `snapshot-name`
+
+
+Description
+-----------
+
+Recursively create ZFS snapshots of `dataset-ro-base` and
+`dataset-skeleton-rw` and all its children. The snapshot has the snapshot
+name `snapshot-name` and must not exist already.
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.
+
+
+See Also
+--------
+
+:manpage:`ftjail(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail-umount-tmpl.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,30 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail-umount-tmpl
+==================
+
+Synopsis
+--------
+
+**ftjail umount-tmpl** `dataset-base-ro` `dataset-skeleton-rw`
+
+**ftjail unmount-tmpl** `dataset-base-ro` `dataset-skeleton-rw`
+
+
+Description
+-----------
+
+Unmount the mounted ZFS datasets `dataset-skeleton-rw` and
+`dataset-base-ro` and all their mounted children.
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.
+
+
+See Also
+--------
+
+:manpage:`ftjail(8)`, :manpage:`ftjail-mount-tmpl(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/ftjail.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,86 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+ftjail
+======
+
+Synopsis
+--------
+
+**ftjail -hV**
+
+**ftjail subcommand**
+
+
+Description
+-----------
+
+Management tool for Thin Jails: creation of base and skeleton datasets,
+mount and population helpers.
+
+The following global options are implemented:
+
+.. program:: ftjail
+
+.. option:: -h
+
+   Print a short usage message to stdout and exit.
+
+.. option:: -V
+
+   Print the program name and version number to stdout and exit.
+
+
+Subcommands
+-----------
+
+:manpage:`ftjail-build-etcupdate-current-tmpl(8)`
+
+    Build a "current" tree suitable for the default and extract mode
+    of \"etcupdate\"
+
+:manpage:`ftjail-copy-skel(8)`
+
+    Recursively copy template skeleton contents into jail-specific datasets
+
+:manpage:`ftjail-datasets-tmpl(8)`
+
+    Create ZFS template datasets for new Thin Jails using base and skeleton
+
+:manpage:`ftjail-freebsd-update(8)`
+
+    A :manpage:`freebsd-update(8)` implementation for Thin Jails
+
+:manpage:`ftjail-mount-tmpl(8)`
+
+    Canonically mount the RO base and the RW skeleton of a Thin Jail
+
+:manpage:`ftjail-umount-tmpl(8)`
+
+    Unmount mounted Thin Jail template datasets
+
+:manpage:`ftjail-interlink-tmpl(8)`
+
+    Create proper symlinks for "skeleton" style Thin Jails
+
+:manpage:`ftjail-populate-tmpl(8)`
+
+    Populate a prepared directory structure with the contents of a
+    FreeBSD base system
+
+:manpage:`ftjail-snapshot-tmpl(8)`
+
+    Recursively create ZFS snapshots of the RO base datasets and the RW
+    skeleton datasets
+
+
+Implementation Notes
+--------------------
+
+All commands with the exception of :command:`ftjail populate-tmpl` and
+:command:`ftjail interlink-tmpl` require ZFS as filesystem.
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fwireguard.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,46 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fwireguard
+==========
+
+Synopsis
+--------
+
+**service fwireguard** [ **start** | **stop** | **reload** | **status** ]
+
+
+Description
+-----------
+
+An :file:`rc.d` script to manage and configure Wireguard interfaces.
+
+
+Configuration Variables
+~~~~~~~~~~~~~~~~~~~~~~~
+
+`fwireguard_enable`
+  Default: NO
+
+`fwireguard_wait`
+  Sleep the given amount of time between configuring the interface and
+  calling the post-start script.
+  Default: 2s
+
+
+`fwireguard_configdir`
+  Default: /usr/local/etc/fwireguard
+
+
+Files
+-----
+
+- :file:`/usr/local/etc/fwireguard`
+
+    Default-directory where all the configuration files for all wireguard
+    interfaces are looked up.
+
+
+See also
+--------
+
+:manpage:`wg(8)`, `rc.conf(5)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fzfs-copy-tree.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,71 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fzfs-copy-tree
+==============
+
+.. program:: fzfs copy-tree
+
+
+Synopsis
+--------
+
+**fzfs copy-tree** [**-A**] [**-M** `mountpoint`] [**-k**] [**-n**] [**-u**] `source-dataset` `dest-dataset`
+
+
+Description
+-----------
+
+Copy the ZFS filesystem or snapshot that is rooted at `source-dataset`
+and all descendent datasets to the destination rooted at `dest-dataset`.
+
+The structure and content of the filesystems is copied.
+
+If `source-dataset` is a snapshot then all of its child datasets also must
+have a snapshot with the same snapshot name.
+
+`dest-dataset` must not exist already.
+
+By default `canmount` is also copied. But this can be modified with
+option :option:`-A`.
+
+
+Options
+-------
+
+.. option:: -A
+
+   Unconditionally set the ZFS property `canmount=noauto` for all
+   created datasets in the destination tree, unless the source dataset
+   has `canmount=off`.
+
+.. option:: -M mountpoint
+
+   Set the `mountpoint` property for the root `dest-dataset` to `mountpoint`.
+   All children will be set to inherit it.
+
+.. option:: -k
+
+   When copying from a snapshot source a corresponding snapshot will be
+   copied to the target dataset. By default this snapshot will be
+   deleted. With this option this snapshot is kept.
+
+.. option:: -n
+
+   Dry-run. Do not really create and copy datasets but show what would
+   be done.
+
+.. option:: -u
+
+   Do not mount the copied datasets.
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.
+
+
+See Also
+--------
+
+:manpage:`fzfs(8)`, :manpage:`fzfs-create-tree(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fzfs-create-tree.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,72 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fzfs-create-tree
+================
+
+.. program:: fzfs create-tree
+
+
+Synopsis
+--------
+
+**fzfs create-tree** [**-A**] [**-M** `mountpoint`] [**-n**] [**-p**] [**-u**] `source-dataset` `dest-dataset`
+
+Description
+-----------
+
+Copy the ZFS filesystem structure that is rooted at `source-dataset`
+to the destination rooted at `dest-dataset`.
+
+The content of the filesystems is **not** copied. This is also true for
+permissions and/or ACLs.
+
+`dest-dataset` must not exist already.
+
+By default some important dataset properties that are set locally in
+the source tree are copied to the destination. This includes `atime`,
+`exec`, `setuid`, `compression`, `primarycache`, `sync` and
+`readonly`.
+
+By default `canmount` is also copied. But this can be modified with
+options :option:`-A` and :option:`-p`.
+
+
+Options
+-------
+
+.. option:: -A
+
+   Unconditionally set the ZFS property `canmount=noauto` for all
+   created datasets in the destination tree. This option overwrites
+   option :option:`-p`.
+
+.. option:: -M mountpoint
+
+   Set the `mountpoint` property for the root `dest-dataset` to `mountpoint`.
+   All children will be set to inherit it.
+
+.. option:: -n
+
+   Dry-run. Do not really create datasets but show what would be done.
+
+.. option:: -p
+
+   Copy the `canmount` property only from the source to the root
+   destination dataset This will be overwritten by the option
+   :option:`-A`.
+
+.. option:: -u
+
+   Do not mount the newly created datasets.
+
+
+Environment
+-----------
+
+All environment variables that affect :command:`zfs` are effective also.
+
+
+See Also
+--------
+
+:manpage:`fzfs(8)`, :manpage:`fzfs-copy-tree(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fzfs-mount.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,50 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fzfs-mount
+==========
+
+Synopsis
+--------
+
+**fzfs mount** [**-O**] [**-N**] [**-P**] [**-n**] [**-u**] `dataset` [`mountpoint`]
+
+
+Description
+-----------
+
+Mount the ZFS dataset `dataset` and all its children to mountpoint
+`mountpoint`.
+
+
+Options
+-------
+
+.. program:: fzfs mount
+
+.. option:: -O
+
+   Also mount datasets at mountpoints outside of their "natural"
+   and inherited mountpoints.
+
+.. option:: -N
+
+   Mount at their "natural" configured ZFS mountpoints
+   (the `mountpoint` argument is not required in this case).
+
+.. option:: -P
+
+   Do not mount the given parent `dataset` but only its children.
+
+.. option:: -n
+
+   Do not really mount anything but show what would be mounted where.
+
+.. option:: -u
+
+   Alias of :option:`-n`
+
+
+See Also
+--------
+
+:manpage:`fzfs-umount(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fzfs-umount.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,23 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fzfs-umount
+===========
+
+Synopsis
+--------
+
+**fzfs umount** `dataset`
+
+**fzfs unmount** `dataset`
+
+
+Description
+-----------
+
+Unmount the mounted `dataset` and all its children.
+
+
+See Also
+--------
+
+:manpage:`fzfs-mount(8)`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/fzfs.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,54 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+fzfs
+====
+
+Synopsis
+--------
+
+**fzfs -hV**
+
+**fzfs subcommand**
+
+
+Description
+-----------
+
+Helper for ZFS management:
+
+- recursively mount and unmount a ZFS dataset tree
+- create a ZFS dataset tree stucture from a source tree
+
+The following global options are implemented:  
+
+.. program:: fzfs
+
+.. option:: -h
+
+   Print a short usage message to stdout and exit.
+
+.. option:: -V
+
+   Print the program name and version number to stdout and exit.  
+
+
+Subcommands
+-----------
+
+:manpage:`fzfs-copy-tree(8)`
+
+    Recursively copy a ZFS dataset tree based while copying some
+    important properties also
+
+:manpage:`fzfs-create-tree(8)`
+
+    Recursively create a ZFS dataset tree based on an existing tree while
+    copying some important properties also
+
+:manpage:`fzfs-mount(8)`
+
+    Recursively mount a ZFS dataset and its children
+
+:manpage:`fzfs-umount(8)`
+
+    Recursively unmount a ZFS datasets and its children
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/man/man8/local-bsdtools.rst	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,91 @@
+.. -*- coding: utf-8; indent-tabs-mode: nil; -*-
+
+.. local-bsdtools documentation master file, created by
+   sphinx-quickstart on Sat Sep 17 21:32:09 2022.
+
+
+local-bsdtools
+==============
+
+Description
+-----------
+
+A collection of helper tools specific for FreeBSD.
+
+They help with
+
+- managing the installed ports and packages
+- creating, installing and updating jails
+- creating, installing and updating "thin jails"
+- recursively mounting of ZFS dataset hierarchies
+
+The package contains also a set of daily periodic scripts to
+
+- trim ZFS pools regularly
+- update the IPv6 neighbour cache
+- handle (file-)triggered actions (e.g. to indirectly handle certbot
+  deploy actions)
+
+All the periodic scripts are disabled by default.
+
+
+See Also
+--------
+
+- :manpage:`bsmtp2dma.conf(5)`
+- :manpage:`local-bsdtools-periodic(5)`
+- :manpage:`package-mapping.conf(5)`
+- :manpage:`pkgtools.conf(5)`
+
+- :manpage:`bsmtp2dma(8)`
+- :manpage:`check-ports(8)`
+- :manpage:`fjail(8)`
+
+  * :manpage:`fjail-configure(8)`
+  * :manpage:`fjail-copy(8)`
+  * :manpage:`fjail-datasets(8)`
+  * :manpage:`fjail-hostid(8)`
+  * :manpage:`fjail-mount(8)`
+  * :manpage:`fjail-populate(8)`
+  * :manpage:`fjail-privs(8)`
+  * :manpage:`fjail-umount(8)`
+
+- :manpage:`fjail-copy(8)`
+
+- :manpage:`ftjail(8)`
+
+  * :manpage:`ftjail-build-etcupdate-current-tmpl(8)`
+  * :manpage:`ftjail-copy-skel(8)`
+  * :manpage:`ftjail-datasets-tmpl(8)`
+  * :manpage:`ftjail-freebsd-update(8)`
+  * :manpage:`ftjail-interlink-tmpl(8)`
+  * :manpage:`ftjail-mount-tmpl(8)`
+  * :manpage:`ftjail-populate-tmpl(8)`
+  * :manpage:`ftjail-snapshot-tmpl(8)`
+  * :manpage:`ftjail-umount-tmpl(8)`
+
+- :manpage:`fpkg(8)`
+
+- :manpage:`fwireguard(8)`
+  
+- :manpage:`fzfs(8)`
+
+  * :manpage:`fzfs-copy-tree(8)`
+  * :manpage:`fzfs-create-tree(8)`
+  * :manpage:`fzfs-mount(8)`
+  * :manpage:`fzfs-umount(8)`
+
+
+Environment
+-----------
+
+Because the tools use different helper tools like :command:`zfs` or
+:command:`pkg` the relevant environment variables according to this tools
+also do apply generally.
+
+
+History
+-------
+
+:Version: @(#)@@SIMPLEVERSIONTAG@@
+:ID:      $HGid$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/etc/bsmtp2dma.conf.sample	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,15 @@
+#
+# Configure bsmtp2dma tool.
+#
+# Is is sourced by `bsmtp2dma`.
+#
+# @(#)@@SIMPLEVERSIONTAG@@
+#
+
+#
+# MAILER should be compatible to "sendmail":
+# - read mail including headers from stdin until EOF
+# - support the "-f" flag to explicitely set the envelope sender
+#
+#MAILER="/usr/sbin/sendmail"
+#MAILER="/usr/libexec/dma"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/etc/package-mapping.conf.sample	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,7 @@
+#
+# Map from installed package name to the (unchanged) parent package name
+#
+# @(#)@@SIMPLEVERSIONTAG@@
+#
+fmg-nextcloud-php71 nextcloud-php71
+fmg-nextcloud-twofactor_totp-php71 nextcloud-twofactor_totp-php71
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/etc/periodic/daily/720.local-triggered-action	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,169 @@
+#!/bin/sh
+# -*- indent-tabs-mode: nil; -*-
+#
+# @(#)@@SIMPLEVERSIONTAG@@
+#
+# Daily script to handle actions triggered by newly existing files.
+# This is e.g. convenient to notify a running daemon to reload because
+# of renewed certificates.
+#
+
+# If there is a global system configuration file, suck it in.
+if [ -r /etc/defaults/periodic.conf ]
+then
+    . /etc/defaults/periodic.conf
+    source_periodic_confs
+fi
+
+# Set it to "YES" to enable this script
+: ${daily_local_triggered_action_enable:=NO}
+# The readability of any of the given files triggers the action
+: ${daily_local_triggered_action_files=}
+#
+# A condition to check also before executing an action.
+#
+# May be a Shell pipeline.
+# E.g. "service nginx onestatus || service apache2 onestatus"
+#
+: ${daily_local_triggered_action_condition=}
+#
+# The action to execute.
+#
+# May be a Shell pipeline.
+# E.g. "{ service nginx onereload && service apache2 onereload ; } || true"
+#
+: ${daily_local_triggered_action_action=}
+#
+# By default all files triggering the action are removed. Set to "NO" if
+# the files should remain.
+#
+: ${daily_local_triggered_action_files_remove:=YES}
+#
+# If profiles are defined this script is re-executed once for for every
+# profile with the profile as parameter.
+# No global options above besides "daily_local_triggered_action_enable" are
+# used.
+# Instead profile level configurations are named
+# daily_local_triggered_action_<profile>_<option> .
+#
+: ${daily_local_triggered_action_profiles=}
+
+
+#:
+#: Check whether a given profile is defined in the configuration
+#:
+#: Args:
+#:  $1: The profile to check for
+#:
+#: Returns:
+#:   0: If the profile is found
+#:   1: If the profile is not found
+#:
+_is_profile() {
+    local prof
+
+    for prof in ${daily_local_triggered_action_profiles}; do
+	if [ "${prof}" = "$1" ]; then
+	    return 0
+	fi
+    done
+    return 1
+}
+
+
+rc=0
+
+if [ -n "${daily_local_triggered_action_profiles}" ]; then
+    if [ $# -eq 1 ]; then
+        profile="$1"
+        if ! _is_profile "${profile}"; then
+            echo "ERROR: no such profile: ${profile}" 1>&2
+            exit 1
+        fi
+        profilevar="$(echo -n "${profile}" | /usr/bin/tr -- '-:.@/$*+~=!()|' '_')"
+        eval daily_local_triggered_action_files="\"\${daily_local_triggered_action_${profilevar}_files-}\""
+        eval daily_local_triggered_action_condition="\"\${daily_local_triggered_action_${profilevar}_condition-}\""
+        eval daily_local_triggered_action_action="\"\${daily_local_triggered_action_${profilevar}_action-}\""
+        eval daily_local_triggered_action_files_remove="\${daily_local_triggered_action_${profilevar}_files_remove:-YES}"
+    elif [ $# -gt 1 ]; then
+        echo "ERROR: usage" 1>&2
+        exit 1
+    else
+        for _p in ${daily_local_triggered_action_profiles} ; do
+            # Re-execute with profile
+            $0 "${_p}"
+            _tmprc=$?
+            if [ ${_tmprc} -ne 0 ]; then
+                rc=${_tmprc}
+            fi
+        done
+        exit ${rc}
+    fi
+fi
+
+case "${daily_local_triggered_action_enable}" in
+    [Yy][Ee][Ss])
+        if [ -z "${profile}" ]; then
+            profilestr=
+        else
+            profilestr=" (${profile})"
+        fi
+
+        echo
+        echo "Testing for newly triggered action${profilestr}"
+
+        _do_action=""
+        TRIGGER_FILES=""
+
+        for _f in ${daily_local_triggered_action_files}; do
+            if [ -r "${_f}" ]; then
+                _do_action="yes"
+                TRIGGER_FILES="${TRIGGER_FILES} ${_f}"
+            fi
+        done
+
+        if [ "${_do_action}" = "yes" ]; then
+            if [ -z "${daily_local_triggered_action_action}" ]; then
+                echo "ERROR: no action defined${profilestr}" 1>&2
+                exit 2
+            fi
+
+            echo "Executing action because valid trigger found${profilestr}"
+            if [ -n "${daily_local_triggered_action_condition}" ]; then
+                eval ${daily_local_triggered_action_condition}
+                _tmprc=$?
+                if [ ${_tmprc} -eq 0 ]; then
+                    eval ${daily_local_triggered_action_action}
+                    rc=$?
+                    if [ ${rc} -ne 0 ] ; then
+                        echo "ERROR: Action failed${profilestr}" 1>&2
+                    fi
+                else
+                    rc=1
+                fi
+            else
+                eval ${daily_local_triggered_action_action}
+                rc=$?
+            fi
+
+            # Remove trigger files if configured to do so
+            if [ ${rc} -eq 0 ]; then
+                case "${daily_local_triggered_action_files_remove}" in
+                    [Yy][Ee][Ss])
+                        echo "Removing trigger files${profilestr} ..."
+                        for _rf in ${TRIGGER_FILES}; do
+                            rm -fv "${_rf}"
+                        done
+                        ;;
+                esac
+            fi
+        else
+            echo "No action triggers found${profilestr}"
+        fi
+        ;;
+    *)
+        rc=0
+        ;;
+esac
+
+exit ${rc}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/etc/periodic/daily/750.local-trim-zfs	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,122 @@
+#!/bin/sh
+#
+# # @(#)@@SIMPLEVERSIONTAG@@
+#
+# Heavily inspired (aka "copied")  from /etc/periodic/daily/800.scrub-zfs
+#
+
+# If there is a global system configuration file, suck it in.
+#
+
+newline="
+" # A single newline
+
+if [ -r /etc/defaults/periodic.conf ]
+then
+    . /etc/defaults/periodic.conf
+    source_periodic_confs
+fi
+
+: ${daily_local_trim_zfs_enable:=NO}
+: ${daily_local_trim_zfs_pools=}
+: ${daliy_local_trim_zfs_default_threshold:=35}
+# \${daily_local_trim_zfs_$(echo "${pool}"|tr  ".:-" "_")_threshold}
+
+case "$daily_local_trim_zfs_enable" in
+    [Yy][Ee][Ss])
+	echo
+	echo 'TRIM of ZFS pools:'
+
+	if [ -z "${daily_local_trim_zfs_pools}" ]; then
+		daily_local_trim_zfs_pools="$(zpool list -H -o name)"
+	fi
+
+	rc=0
+	for pool in ${daily_local_trim_zfs_pools}; do
+		# sanity check
+		_status=$(zpool list "${pool}" 2> /dev/null)
+		if [ $? -ne 0 ]; then
+			rc=2
+			echo "   WARNING: pool '${pool}' specified in"
+			echo "            '/etc/periodic.conf:daily_local_trim_zfs_pools'"
+			echo "            does not exist"
+			continue
+		fi
+		_status=${_status##*$newline}
+		case ${_status} in
+		*FAULTED*)
+			rc=3
+			echo "Skipping faulted pool: ${pool}"
+			continue ;;
+		*UNAVAIL*)
+			rc=4
+			echo "Skipping unavailable pool: ${pool}"
+			continue ;;
+		esac
+
+		# determine how many days shall be between trums
+		eval _pool_threshold=\${daily_local_trim_zfs_$(echo "${pool}"|tr  ".:-" "_")_threshold}
+		if [ -z "${_pool_threshold}" ]; then
+			_pool_threshold=${daliy_local_trim_zfs_default_threshold}
+		fi
+
+		_last_local_trim=$(zpool history ${pool} | \
+		    egrep "^[0-9\.\:\-]{19} zpool trim( -w)? ${pool}\$" | tail -1 |\
+		    cut -d ' ' -f 1)
+		if [ -z "${_last_local_trim}" ]; then
+			# creation time of the pool if no trim was done
+			_last_local_trim=$(zpool history ${pool} | \
+			    sed -ne '2s/ .*$//p')
+		fi
+		if [ -z "${_last_local_trim}" ]; then
+			echo "   skipping TRIM of pool '${pool}':"
+			echo "      can't get last TRIM date"
+			continue
+		fi
+
+		# Now minus last trim (both in seconds) converted to days.
+		_local_trim_diff=$(expr -e \( $(date +%s) - \
+		    $(date -j -v -70M -f %F.%T ${_last_local_trim} +%s) \) / 60 / 60 / 24)
+		if [ ${_local_trim_diff} -lt ${_pool_threshold} ]; then
+			echo "   skipping TRIM of pool '${pool}':"
+			echo "      last TRIM is ${_local_trim_diff} days ago, threshold is set to ${_pool_threshold} days"
+			continue
+		fi
+
+		# Check general pool status (as in scrub-zfs)
+		_status="$(zpool status ${pool} | grep scan:)"
+		case "${_status}" in
+			*"scrub in progress"*)
+				echo "   scrubbing of pool '${pool}' in progress, skipping:"
+				continue
+				;;
+			*"resilver in progress"*)
+				echo "   resilvering of pool '${pool}' is in progress, skipping:"
+				continue
+				;;
+			*)
+				# VOID
+				;;
+		esac
+
+		# Check whether a trim is already running
+		_status="$(zpool status ${pool} | fgrep trimming)"
+		case "${_status}" in
+			*\(trimming\)*)
+				echo "   TRIM of pool '${pool}' already in progress, skipping:"
+				;;
+			*)
+				echo "   starting TRIM of pool '${pool}':"
+				zpool trim -w ${pool}
+				[ $rc -eq 0 ] && rc=1
+				;;
+		esac
+	done
+	;;
+
+    *)
+	rc=0
+	;;
+esac
+
+exit $rc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/etc/periodic/daily/800.local-ipv6-refresh	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+# @(#)@@SIMPLEVERSIONTAG@@
+#
+# Daily script to keep the IPv6 routing working
+#
+
+# If there is a global system configuration file, suck it in.
+if [ -r /etc/defaults/periodic.conf ]
+then
+    . /etc/defaults/periodic.conf
+    source_periodic_confs
+fi
+
+#
+# Default configuration variables
+#
+: ${daily_local_ipv6_refresh_enable:="NO"}
+: ${daily_local_ipv6_refresh_flags="-n -w2 -q2 -m1 -I"}
+: ${daily_local_ipv6_refresh_target=""}
+
+rc=0
+
+# Go on only if this script is enabled in the configuration
+case "${daily_local_ipv6_refresh_enable}" in
+[Yy][Ee][Ss])
+    ;;
+[Nn][Oo])
+    exit 0
+    ;;
+*)
+    echo "$0: WARNING: \$daily_local_ipv6_refresh_enable is not set properly - assuming \"NO\" has been set" 1>&2
+    exit 0
+    ;;
+esac
+
+if [ -z "${daily_local_ipv6_refresh_target}" ]; then
+    daily_local_ipv6_refresh_target="$(/usr/sbin/sysrc -i -n ipv6_defaultrouter)"
+fi
+
+case "${daily_local_ipv6_refresh_target}" in
+[Nn][Oo])
+    # SKIP because no target or no static IPv6 router given
+    echo "$0: WARNING: \$daily_local_ipv6_refresh_target is \`NO'" 1>&2
+    ;;
+'')
+    # SKIP because this is an unresolvable router specification
+    echo "$0: WARNING: \$ipv6_defaultrouter has an unexpected empty value" 1>&2
+    ;;
+*)
+    #if type anticongestion >/dev/null 2>&1; then
+    #    anticongestion_sleeptime=60
+    #    anticongestion
+    #fi
+
+    # Wait up to 10 seconds to avoid router congestion
+    /bin/sleep $(/usr/bin/jot -r 1 0 10)
+    /usr/sbin/traceroute6 ${daily_local_ipv6_refresh_flags} "${daily_local_ipv6_refresh_target}"
+    rc=$?
+    ;;
+esac
+
+exit $rc
--- a/etc/periodic/daily/800.scrub-zfs	Thu Jun 13 17:30:24 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-#!/bin/sh
-#
-# $FreeBSD$
-#
-
-# If there is a global system configuration file, suck it in.
-#
-
-newline="
-" # A single newline
-
-if [ -r /etc/defaults/periodic.conf ]
-then
-    . /etc/defaults/periodic.conf
-    source_periodic_confs
-fi
-
-: ${daily_scrub_zfs_default_threshold=35}
-
-case "$daily_scrub_zfs_enable" in
-    [Yy][Ee][Ss])
-	echo
-	echo 'Scrubbing of zfs pools:'
-
-	if [ -z "${daily_scrub_zfs_pools}" ]; then
-		daily_scrub_zfs_pools="$(zpool list -H -o name)"
-	fi
-
-	rc=0
-	for pool in ${daily_scrub_zfs_pools}; do
-		# sanity check
-		_status=$(zpool list "${pool}" 2> /dev/null)
-		if [ $? -ne 0 ]; then
-			rc=2
-			echo "   WARNING: pool '${pool}' specified in"
-			echo "            '/etc/periodic.conf:daily_scrub_zfs_pools'"
-			echo "            does not exist"
-			continue
-		fi
-		_status=${_status##*$newline}
-		case ${_status} in
-		*FAULTED*)
-			rc=3
-			echo "Skipping faulted pool: ${pool}"
-			continue ;;
-		*UNAVAIL*)
-			rc=4
-			echo "Skipping unavailable pool: ${pool}"
-			continue ;;
-		esac
-
-		# determine how many days shall be between scrubs
-		eval _pool_threshold=\${daily_scrub_zfs_$(echo "${pool}"|tr  ".:-" "_")_threshold}
-		if [ -z "${_pool_threshold}" ];then
-			_pool_threshold=${daily_scrub_zfs_default_threshold}
-		fi
-
-		_last_scrub=$(zpool history ${pool} | \
-		    egrep "^[0-9\.\:\-]{19} zpool scrub ${pool}\$" | tail -1 |\
-		    cut -d ' ' -f 1)
-		if [ -z "${_last_scrub}" ]; then
-			# creation time of the pool if no scrub was done
-			_last_scrub=$(zpool history ${pool} | \
-			    sed -ne '2s/ .*$//p')
-		fi
-		if [ -z "${_last_scrub}" ]; then
-			echo "   skipping scrubbing of pool '${pool}':"
-			echo "      can't get last scrubbing date"
-			continue
-		fi
-
-		# Now minus last scrub (both in seconds) converted to days.
-		_scrub_diff=$(expr -e \( $(date +%s) - \
-		    $(date -j -v -70M -f %F.%T ${_last_scrub} +%s) \) / 60 / 60 / 24)
-		if [ ${_scrub_diff} -lt ${_pool_threshold} ]; then
-			echo "   skipping scrubbing of pool '${pool}':"
-			echo "      last scrubbing is ${_scrub_diff} days ago, threshold is set to ${_pool_threshold} days"
-			continue
-		fi
-
-		_status="$(zpool status ${pool} | grep scan:)"
-		case "${_status}" in
-			*"scrub in progress"*)
-				echo "   scrubbing of pool '${pool}' already in progress, skipping:"
-				;;
-			*"resilver in progress"*)
-				echo "   resilvering of pool '${pool}' is in progress, skipping:"
-				;;
-			*"none requested"*)
-				echo "   starting first scrub (since reboot) of pool '${pool}':"
-				zpool scrub ${pool}
-				[ $rc -eq 0 ] && rc=1
-				;;
-			*)
-				echo "   starting scrub of pool '${pool}':"
-				zpool scrub ${pool}
-				[ $rc -eq 0 ] && rc=1
-				;;
-		esac
-
-		echo "      consult 'zpool status ${pool}' for the result"
-	done
-	;;
-
-    *)
-	rc=0
-	;;
-esac
-
-exit $rc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/etc/pkgtools.conf.sample	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,3 @@
+# Shell (sh) script to configure tools in local-bsdtools
+# @(#)@@SIMPLEVERSIONTAG@@
+#
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/fbhyve.in	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,143 @@
+#!/bin/sh
+
+# PROVIDE: bhyve
+# REQUIRE: LOGIN
+# KEYWORD: nojail
+#
+
+#
+# Add the following lines to /etc/rc.conf to enable bhyve:
+# bhyve_enable (bool):  Set to "NO" by default.
+#                       Set it to "YES" to enable bhyve
+# bhyve_profiles (str): Set to "" by default.
+#                       Define your profiles here.
+# bhyve_tapdev (str):   Set to "tap0" by default.
+#                       Set to the tap(4) device to use.
+# bhyve_diskdev (str):  Must be set, no default.
+#                       Set to the disk device to use.
+# bhyve_ncpu (int):     Set to 1 by default.
+#                       Set to the number of CPUs for the VM.
+# bhyve_memsize (int):  Set to 512 by default.
+#                       Set to the number of MB of memory for the VM.
+
+. /etc/rc.subr
+
+name="bhyve"
+rcvar=bhyve_enable
+
+start_precmd="bhyve_prestart"
+status_cmd="bhyve_status"
+poll_cmd="bhyve_poll"
+stop_cmd="bhyve_stop"
+_session=$name
+command="/usr/local/bin/tmux"
+procname="-sh"
+
+[ -z "$bhyve_enable" ]  && bhyve_enable="NO"
+[ -z "$bhyve_tapdev" ]  && bhyve_tapdev="tap0"
+[ -z "$bhyve_diskdev" ] && bhyve_diskdev="none"
+[ -z "$bhyve_ncpu" ]    && bhyve_ncpu="1"
+[ -z "$bhyve_memsize" ] && bhyve_memsize="512"
+
+load_rc_config $name
+
+if [ -n "$2" ]; then
+	profile="$2"
+	_session="${_session}_${profile}"
+	if [ "x${bhyve_profiles}" != "x" ]; then
+		eval bhyve_enable="\${${_session}_enable:-${bhyve_enable}}"
+		eval bhyve_tapdev="\${${_session}_tapdev:-${bhyve_tapdev}}"
+		eval bhyve_diskdev="\${${_session}_diskdev:-${bhyve_diskdev}}"
+		eval bhyve_ncpu="\${${_session}_ncpu:-${bhyve_ncpu}}"
+		eval bhyve_memsize="\${${_session}_memsize:-${bhyve_memsize}}"
+	else
+		echo "$0: extra argument ignored"
+	fi
+else
+	if [ "x${bhyve_profiles}" != "x" -a "x$1" != "x" ]; then
+		for profile in ${bhyve_profiles}; do
+			eval _enable="\${bhyve_${profile}_enable}"
+			case "x${_enable:-${bhyve_enable}}" in
+			x|x[Nn][Oo]|x[Nn][Oo][Nn][Ee])
+				continue
+				;;
+			x[Yy][Ee][Ss])
+				;;
+			*)
+				if test -z "$_enable"; then
+					_var=bhyve_enable
+				else
+					_var=bhyve_"${profile}"_enable
+				fi
+				echo "Bad value" \
+				    "'${_enable:-${bhyve_enable}}'" \
+				    "for ${_var}. " \
+				    "Profile ${profile} skipped."
+				continue
+				;;
+			esac
+			echo "===> bhyve profile: ${profile}"
+			/usr/local/etc/rc.d/bhyve $1 ${profile}
+			retcode="$?"
+			if [ "0${retcode}" -ne 0 ]; then
+				failed="${profile} (${retcode}) ${failed:-}"
+			else
+				success="${profile} ${success:-}"
+			fi
+		done
+		exit 0
+	fi
+	profile=$name
+fi
+
+pidfile="/var/run/${_session}.pid"
+
+
+bhyve_prestart()
+{
+	case ${bhyve_diskdev} in
+	[Nn][Oo][Nn][Ee] | '')
+		echo "No ${_session}_diskdev set. Quitting." 1>&2
+		return 1;
+		;;
+	esac
+	if [ ! -c "${bhyve_diskdev}" -a ! -f "${bhyve_diskdev}" ]; then
+		echo "${bhyve_diskdev} doesn't exist or is not suitable as a diskdev" 1>&2
+		return 1;
+	fi
+}
+
+bhyve_status()
+{
+	if ${command} has-session -t ${_session} 2>/dev/null; then
+		echo "${_session} is running."
+	else
+		echo "${_session} is not running."
+		return 1
+	fi
+}
+
+bhyve_poll()
+{
+	echo -n "Waiting for session: ${_session}"
+	while ${command} has-session -t ${_session} 2>/dev/null; do
+		sleep 1
+	done
+	echo
+}
+
+bhyve_stop()
+{
+	if ${command} has-session -t ${_session} 2>/dev/null; then
+		echo "Stopping ${_session}."
+		${command} kill-session -t ${_session}
+		while ${command} has-session -t ${_session} 2>/dev/null; do
+			sleep 1
+		done
+	fi
+	rm -f ${pidfile}
+}
+
+command_args="new-session -ds ${_session} \"sh -c 'echo \\\$PPID >${pidfile}; while true; do /usr/sbin/bhyvectl --vm=${_session} --destroy; /usr/sbin/bhyveload -m ${bhyve_memsize} -d ${bhyve_diskdev} ${_session} && /usr/sbin/bhyve -c ${bhyve_ncpu} -m ${bhyve_memsize} -AI -H -P -g 0 -s 0:0,hostbridge -s 1:0,virtio-net,${bhyve_tapdev} -s 2:0,virtio-blk,${bhyve_diskdev} -s 31,lpc -l com1,stdio ${_session} || break; done'\""
+
+run_rc_command "$1"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/files/fwireguard.in	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,119 @@
+#!/bin/sh
+
+# PROVIDE: fwireguard
+# REQUIRE: NETWORKING
+# KEYWORD: shutdown
+
+# @(#)%%SIMPLEVERSIONTAG%%
+
+#
+# fwireguard_enable (bool):    Set to "YES" to enable wireguard (default: "NO")
+# fwireguard_wait (str):       wait (sleep) this time before calling post-start
+#                              when configuring an interface (default: 2s)
+# fwireguard_configdir (str):  Where fwireguard finds its configuration
+#                              (default: %%PREFIX%%/etc/fwireguard)
+#
+# NOTE: All wireguard interfaces must be mentioned fist in "cloned_interfaces".
+#
+
+. /etc/rc.subr
+
+name=fwireguard
+desc="Wireguard startup helper"
+rcvar=fwireguard_enable
+extra_commands="reload status"
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+reload_cmd="${name}_reload"
+status_cmd="${name}_status"
+
+load_rc_config $name
+
+: ${fwireguard_enable:="NO"}
+: ${fwireguard_wait="2s"}
+: ${fwireguard_configdir:="%%PREFIX%%/etc/fwireguard"}
+
+
+fwireguard_start()
+{
+    local _f _if
+
+    if [ ! -d "${fwireguard_configdir}" ]; then
+        mkdir "${fwireguard_configdir}"
+    fi
+    for _if in `/sbin/ifconfig -g wg`; do
+
+        _f="${fwireguard_configdir}/${_if}.key"
+        if [ ! -f "${_f}" ]; then
+            echo "Generating secret key for ${_if} in ${_f}"
+            (umask 0077; /usr/bin/wg genkey > "${_f}")
+        fi
+
+        _f="${fwireguard_configdir}/${_if}.pub"
+        if [ ! -f "${_f}" ]; then
+            echo "Generating public key for ${_if} in ${_f}"
+            /usr/bin/wg pubkey < ${fwireguard_configdir}/${_if}.key > "${_f}"
+        fi
+
+        _f="${fwireguard_configdir}/${_if}.conf"
+        if [ ! -f "${_f}" ]; then
+            echo "Generating minimal config for ${_if} in ${_f}"
+            umask 0077
+            echo "[Interface]"                                  >  "${_f}"
+            /usr/bin/printf 'PrivateKey\t\t= '                  >> "${_f}"
+            /bin/cat "${fwireguard_configdir}/${_if}.key"       >> "${_f}"
+            echo -e "#ListenPort\t\t= 51820"                    >> "${_f}"
+            echo -e "#FwMark\t\t\t= 0x12345678\n"               >> "${_f}"
+            echo "#[Peer]"                                      >> "${_f}"
+            echo -e "#PublicKey\t\t= BlAbLABlA/EtCeTcEtc="      >> "${_f}"
+            echo -e "#AllowedIPs\t\t= 10.X.X.1/32, 10.X.X.2/32" >> "${_f}"
+            echo -e "#PresharedKey\t\t= BlAbLABlA/EtCeTcEtc="   >> "${_f}"
+            echo -e "#Endpoint\t\t= [2001:db8::1]:51820"        >> "${_f}"
+            echo -e "#PersistentKeepalive\t= 30"                >> "${_f}"
+        fi
+
+        /sbin/ifconfig "${_if}" destroy
+        /sbin/ifconfig "${_if}" create    # will take ifconfig_wgX="inet values" from /etc/rc.conf
+        /usr/bin/wg setconf "${_if}" "${_f}"
+        if [ -x "${fwireguard_configdir}/${_if}.post-start" ]; then
+            if [ -n "${fwireguard_wait}" ]; then
+                /bin/sleep "${fwireguard_wait}"
+            fi
+            "${fwireguard_configdir}/${_if}.post-start"
+        fi
+#       /usr/bin/wg syncconf ${_if} ${_f}
+    done
+}
+
+
+fwireguard_stop()
+{
+    local _if
+
+    for _if in `/sbin/ifconfig -g wg`; do
+        if [ -x "${fwireguard_configdir}/${_if}.pre-stop" ]; then
+            "${fwireguard_configdir}/${_if}.pre-stop"
+        fi
+        /sbin/ifconfig "${_if}" down
+    done
+}
+
+
+fwireguard_reload()
+{
+    fwireguard_start
+}
+
+
+fwireguard_status()
+{
+    local _if
+
+    for _if in `/sbin/ifconfig -g wg`; do
+        /usr/bin/wg show "${_if}"
+    done
+}
+
+
+run_rc_command "$1"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg-descr	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,6 @@
+A collection of private local FreeBSD tools for managing the system.
+
+Contains tools to manage jails, thin jails and binary packages.
+
+And the tool "bsmtp2dma" is a simple replacement for Bacula's "bsmtp"
+when the system mailer does not listen on TCP ports.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg-plist	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,46 @@
+@comment FILES
+etc/periodic/daily/720.local-triggered-action
+etc/periodic/daily/750.local-trim-zfs
+etc/periodic/daily/800.local-ipv6-refresh
+sbin/bsmtp2dma
+sbin/check-ports
+sbin/fjail
+sbin/ftjail
+sbin/fpkg
+sbin/fzfs
+share/local-bsdtools/common.subr
+share/examples/local-bsdtools/freebsd-update-ftjail-template.sh
+share/examples/local-bsdtools/freebsd-update-ftjail.sh
+%%DOCS%%share/man/man5/bsmtp2dma.conf.5.gz
+%%DOCS%%share/man/man5/local-bsdtools-periodic.5.gz
+%%DOCS%%share/man/man5/package-mapping.conf.5.gz
+%%DOCS%%share/man/man5/pkgtools.conf.5.gz
+%%DOCS%%share/man/man8/local-bsdtools.8.gz
+%%DOCS%%share/man/man8/bsmtp2dma.8.gz
+%%DOCS%%share/man/man8/check-ports.8.gz
+%%DOCS%%share/man/man8/fjail.8.gz
+%%DOCS%%share/man/man8/fjail-configure.8.gz
+%%DOCS%%share/man/man8/fjail-freebsd-update.8.gz
+%%DOCS%%share/man/man8/fjail-hostid.8.gz
+%%DOCS%%share/man/man8/fpkg.8.gz
+%%DOCS%%share/man/man8/ftjail.8.gz
+%%DOCS%%share/man/man8/ftjail-build-etcupdate-current-tmpl.8.gz
+%%DOCS%%share/man/man8/ftjail-copy-skel.8.gz
+%%DOCS%%share/man/man8/ftjail-datasets-tmpl.8.gz
+%%DOCS%%share/man/man8/ftjail-freebsd-update.8.gz
+%%DOCS%%share/man/man8/ftjail-interlink-tmpl.8.gz
+%%DOCS%%share/man/man8/ftjail-mount-tmpl.8.gz
+%%DOCS%%share/man/man8/ftjail-populate-tmpl.8.gz
+%%DOCS%%share/man/man8/ftjail-snapshot-tmpl.8.gz
+%%DOCS%%share/man/man8/ftjail-umount-tmpl.8.gz
+%%DOCS%%share/man/man8/fwireguard.8.gz
+%%DOCS%%share/man/man8/fzfs.8.gz
+%%DOCS%%share/man/man8/fzfs-copy-tree.8.gz
+%%DOCS%%share/man/man8/fzfs-create-tree.8.gz
+%%DOCS%%share/man/man8/fzfs-mount.8.gz
+%%DOCS%%share/man/man8/fzfs-umount.8.gz
+@sample %%ETCDIR%%/bsmtp2dma.conf.sample
+@sample %%ETCDIR%%/package-mapping.conf.sample
+@sample %%ETCDIR%%/pkgtools.conf.sample
+@comment DIRECTORIES
+@dir %%ETCDIR%%
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/bsmtp2dma	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,337 @@
+#!/bin/sh
+# -*- indent-tabs-mode: nil; -*-
+: 'A simple replacement for Bacula `bsmtp` when the underlying mailer does
+not listen on TCP ports (e.g. `dma`, `ssmtp` et al.).
+
+:Author:    Franz Glasner
+:Copyright: (c) 2019-2024 Franz Glasner.
+            All rights reserved.
+:License:   BSD 3-Clause "New" or "Revised" License.
+            See LICENSE for details.
+            If you cannot find LICENSE see
+            <https://opensource.org/licenses/BSD-3-Clause>
+:ID:        @(#)@@SIMPLEVERSIONTAG@@
+
+'
+
+VERSION="@@VERSION@@"
+
+USAGE='
+USAGE: bsmtp2dma [OPTIONS] RECIPIENT ...
+
+Options:
+
+  -V           Show the program version and usage and exit.
+
+  -8           Does nothing. Just a compatibility option for `bsmtp`.
+
+  -c ADDRESS   Set the "CC:" header.
+
+  -d n         Does nothing. Just a compatibility option for `bsmtp`.
+
+  -f ADDRESS   Set the "From:" header.
+
+  -h MAILHOST:PORT  Does nothing. Just a compatibility option for `bsmtp`.
+
+  -l NUMBER    Does nothing. Just a compatibility option for `bsmtp`.
+
+  -r ADDRESS   Set the "Reply-To:" header
+
+  -s SUBJECT   Set the "Subject:" header
+
+
+Files:
+
+  The shell style configuration file in 
+
+      @@ETCDIR@@/bsmtp2dma.conf is
+
+  sourced in at script start.
+
+'
+
+#
+# Configuration directory
+#
+: ${CONFIGDIR:=@@ETCDIR@@}
+
+test -r "${CONFIGDIR}/bsmtp2dma.conf" && . "${CONFIGDIR}/bsmtp2dma.conf"
+
+
+#
+# Default configuration values
+#
+# `sendmail` is also valid for `dma` because of the mapping within
+#  `/etc/mail/mailer.conf`
+#
+: ${MAILER:=/usr/sbin/sendmail}
+
+
+parse_addr() {
+    : 'Parse an possibly complex email address.
+
+    Addresses can be of the form
+
+    - Name Parts <user@domain.tld>
+    - user@domain.tld
+
+    `Name Parts` may not contain ``<`` or ``>`` characters.
+
+    Args:
+        _addr: the complex email address
+
+    Returns:
+        0 on success, 1 on errors
+
+    Output (Globals):
+        email_name: the name part (or empty)
+        email_addr: the technical address part (or empty)
+
+    '
+    local _addr
+
+    _addr="$1"
+    test -n "${_addr}" || return 1
+
+    if printf "%s" "${_addr}" | grep -q -E -e '^[^<>]+<[^<>]+@[^<>]+>$'; then
+        email_name=$(printf '%s' "${_addr}" | sed -E -e 's/[[:space:]]*<.+$//')
+        email_addr=$(printf '%s' "${_addr}" | sed -E -e 's/^[^<>]+<//' | sed -E -e 's/>$//')
+        return 0
+    fi
+    if printf "%s" "${_addr}" | grep -q -E -e '^[^<>]+@[^<>]+$'; then
+        email_name=""
+        email_addr="${_addr}"
+        return 0
+    fi
+    return 1
+}
+
+
+send_mail() {
+    : 'Send the mail via the underlying configured mailer (dma, sendmail et al.).
+
+    Args:
+        _recipient: The recipient name.
+
+                    Will be written into the "To:" header also.
+
+    Input (Globals):
+        MAILER
+        MAILCONTENT
+        MAILFIFO_STDIN
+        MAILFIFO_STDOUT
+        CC
+        FROM
+        REPLYTO
+        SUBJECT
+
+    Returns:
+        0 on success, other values on errors or the error exit code from the
+        underlying mailer
+
+    This procedure starts the configured mailer as coproc and sends
+    email headers and contents to the started mailer.
+
+    '
+    local _recipient _rc _oifs _text _pid_mailer _recipient_addr
+    local _from_from _from_addr _sender_addr _dummy
+
+    _recipient="$1"
+    _rc=0
+
+    if parse_addr "${_recipient}"; then
+        _recipient_addr="${email_addr}"
+    else
+        echo "ERROR: unknown recipient address format in \`${_recipient}'" >&2
+        return 1
+    fi
+    _sender_addr="$(whoami)@$(hostname -f)"
+    if [ -z "${FROM}" ]; then
+        _from_addr="${_sender_addr}"
+        _from_from="${_from_addr}"
+    else
+       if parse_addr "${FROM}"; then
+           _from_from="${FROM}"
+           _from_addr="${email_addr}"
+       else
+           echo "ERROR: unknown sender name in \`${FROM}'" >&2
+           return 1
+       fi
+    fi
+
+    mkfifo -m 0600 "${MAILFIFO_STDIN}"
+    _rc=$?
+    if [ ${_rc} -ne 0 ]; then
+        return ${_rc}
+    fi
+    mkfifo -m 0600 "${MAILFIFO_STDOUT}"
+    _rc=$?
+    if [ ${_rc} -ne 0 ]; then
+        rm -f "${MAILFIFO_STDIN}"
+        return ${_rc}
+    fi
+
+    #
+    # Start the mailer **before** opening the pipe; otherwise a
+    # deadlock occurs
+    #
+    "$MAILER" -f "${_sender_addr}" "${_recipient_addr}" <${MAILFIFO_STDIN} >${MAILFIFO_STDOUT} &
+    _pid_mailer=$!
+
+    exec 3>"${MAILFIFO_STDIN}"
+    exec 4<"${MAILFIFO_STDOUT}"
+
+    printf "To: %s\n" "${_recipient}" >&3
+    printf "From: %s\n" "${_from_from}" >&3
+    if [ "${_sender_addr}" != "${_from_addr}" ]; then
+        printf "Sender: %s\n" "${_sender_addr}" >&3
+    fi
+    if [ -n "${SUBJECT}" ]; then
+        printf "Subject: %s\n" "${SUBJECT}" >&3
+    fi
+    if [ -n "${REPLYTO}" ]; then
+        #
+        # XXX TBD proper Reply-To header value checks:
+        #     a comma separated list of full mail addresses
+        #
+        printf "Reply-To: %s\n" "${REPLYTO}" >&3
+    fi
+    if [ -n "${CC}" ]; then
+        #
+        # XXX TBD proper CC header value checks:
+        #     a comma separated list of full mail addresses
+        #
+        printf "Cc: %s\n" "${CC}" >&3
+    fi
+    printf "\n" >&3
+
+    # preserve leading white space when reading with `read`
+    _oifs="$IFS"
+    IFS="
+"
+    cat "${MAILCONTENT}" |
+        while read _text; do
+            printf "%s\n" "$_text" >&3
+        done
+    # not all mailer recognize this
+    # printf ".\n" >&3
+    IFS="$_oifs"
+
+    # close the fd to the pipe: coproc should get EOF and terminate
+    exec 3>&-
+    # read eventually remaining stuff from the mailer until EOF
+    IFS='' read _dummy <&4
+    exec 4<&-
+
+    wait $_pid_mailer
+    _rc=$?
+
+    # we are done with the named pipes
+    rm -f "${MAILFIFO_STDIN}"
+    rm -f "${MAILFIFO_STDOUT}"
+
+    return ${_rc}
+}
+
+
+while getopts "V8c:d:f:h:l:nr:s:" _opt; do
+    case ${_opt} in
+        V)
+            printf 'bsmtp2dma %s\n' '@@SIMPLEVERSIONSTR@@'
+            echo "$USAGE"
+            exit 0;
+            ;;
+        8)
+            : # VOID
+            ;;
+        c)
+            CC="$OPTARG"
+            ;;
+        d)
+            : # VOID
+            ;;
+        f)
+            FROM="$OPTARG"
+            ;;
+        h)
+            : # VOID
+            ;;
+        l)
+            : # VOID
+            ;;
+        r)
+            REPLYTO="$OPTARG"
+            ;;
+        s)
+            SUBJECT="$OPTARG"
+            ;;
+        \?)
+            exit 2;
+            ;;
+        *)
+            echo "ERROR: inconsistent option handling" >&2
+            exit 2;
+            ;;
+    esac
+done
+
+# return code
+_rc=0
+
+MAILTMPDIR="$(mktemp -d)"
+MAILFIFO_STDIN="${MAILTMPDIR}/mail-stdin"
+MAILFIFO_STDOUT="${MAILTMPDIR}/mail-stdout"
+MAILCONTENT="${MAILTMPDIR}/mail-text"
+
+#
+# Clean up existing temporary stuff on all sorts of exit
+# (including the "exit" call (signal 0))
+#
+trap 'if [ -d "${MAILTMPDIR}" ]; then rm -rf "${MAILTMPDIR}"; fi; exit;' 0 1 2 15
+
+test -d "${MAILTMPDIR}" || { echo "ERROR: no existing private tmp dir" >&2; exit 1; }
+
+#
+# Reset the Shell's option handling system to prepare for handling
+# other arguments and probably command-local options
+#
+shift $((OPTIND-1))
+OPTIND=1
+
+# early check whether some recipients are given
+if [ $# -eq 0 ]; then
+    echo "ERROR: no recipient given" >&2
+    exit 2;
+fi
+
+#
+# Collect the mail text from stdin into a temporary file
+#
+exec 3>"${MAILCONTENT}"
+# preserve leading white space when reading with `read`
+_oifs="$IFS"
+IFS="
+"
+while read _text; do
+    if [ "${_text}" = "." ]; then
+        break
+    else
+        printf "%s\n" "${_text}" >&3
+    fi
+done
+exec 3>&-
+IFS="$_oifs"
+
+#
+# Now send the content of the collected mail content to all recipients
+#
+until [ $# -eq 0 ]; do
+    send_mail "$1"
+    _rcsm=$?
+    if [ \( ${_rcsm} -ne 0 \) -a \( ${_rc} -eq 0 \) ]; then
+        _rc=${_rcsm}
+    fi
+    shift
+done
+
+exit ${_rc}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/check-ports	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,565 @@
+#!/bin/sh
+# -*- indent-tabs-mode: nil; -*-
+: 'Check the version status of installed ports and compare them to
+version in remote repositories and the local ports index.
+
+:Author:    Franz Glasner
+:Copyright: (c) 2017-2024 Franz Glasner.
+            All rights reserved.
+:License:   BSD 3-Clause "New" or "Revised" License.
+            See LICENSE for details.
+            If you cannot find LICENSE see
+            <https://opensource.org/licenses/BSD-3-Clause>
+:ID:        @(#)@@SIMPLEVERSIONTAG@@
+
+'
+
+VERSION='@@VERSION@@'
+
+USAGE='
+USAGE: check-ports [options] [args...]
+
+Options:
+
+  -V   Print the program name and version number to stdout and exit
+
+  -h   Print this help message to stdout and exit
+
+  -A   Print for every package the status of all repositories
+
+  -a   Print the data of all repos that have the package
+
+  -n   Print the status of given packages in `args` in all details.
+       No other options are respected.
+
+  -s   Print the status of all packages that need some attention
+
+  -v   Print the title and repository of every installed package always
+
+Per Default (without any option) the status of every package is
+printed with respect to repositories that have the package and have
+differing versions.
+'
+
+#
+# Configuration directory
+#
+: ${CONFIGDIR:=@@ETCDIR@@}
+
+test -r "${CONFIGDIR}/pkgtools.conf" && . "${CONFIGDIR}/pkgtools.conf"
+
+#
+# Mapping configuration: installed package name -> original package name
+# Note: This is independent of any repo
+#
+: ${PACKAGE_MAPPING:=${CONFIGDIR}/package-mapping.conf}
+
+#
+# Local repository with non-public packages and/or ports with changed
+# OPTIONS (i.e. not using the defaults) or forks of official packages with
+# other package names.
+# This repo is strictly *local* to the host and/or jail.
+#
+: ${LOCAL_REPO:=LocalRepo}
+
+#
+# Shared local repository with non-public packages and/or ports with
+# changed OPTIONS (i.e. not using the defaults).
+# Contrary to LOCAL_REPO this repository may be shared.
+#
+: ${SHARED_LOCAL_REPO:=SharedLocalRepo}
+
+#
+# (Shared) repository with ports with default OPTIONS (i.e. unchanged)
+# but newer than the packages in the "FreeBSD" repository.
+# Can also contain non-FreeBSD *public* packages.
+# May be shared.
+# Some sort of a fast-track repository.
+#
+: ${LOCALBSDPORTS_REPO:=LocalBSDPorts}
+
+#
+# The official FreeBSD binary repository
+#
+: ${FREEBSD_REPO:=FreeBSD}
+
+#
+# Directly installed from ports
+#
+: ${PORTS_DIRECT_INSTALLED_REPO:=unknown-repository}
+
+#
+# For the workaround of the bug in pkg rquery -I
+#
+: ${PORTSDIR:=/usr/ports}
+
+
+test_exists_local_index() {
+    : 'Determine whether there exists a ports directory with an index
+    file.
+
+    Returns:
+        status 0 iff the local index exists
+    '
+    pkg version -I -n DUMMY >/dev/null 2>/dev/null
+}
+
+
+get_ports_index_directory() {
+    : 'Ask the packager configuration for the `INDEXDIR` and/or `PORTSDIR`
+    configuration value: either `INDEXDIR` or -- if `INDEXDIR` is empty --
+    `PORTSDIR` is used.
+
+    Output (stdout)
+        the directory where the index database file lives
+
+    '
+    local _dir
+
+    _dir="$(pkg config INDEXDIR)"
+    if [ -z "${_dir}" ]; then
+        _dir="$(pkg config PORTSDIR)"
+    fi
+    printf '%s' "${_dir}"
+}
+
+
+get_ports_index_version() {
+    : 'Determine for package `_package` the version of the package in the
+    local ports index.
+
+    Args:
+        _package: the package name to search for
+
+    Returns:
+        0 on success, 1 on errors or if the package is not in the local
+        ports index
+
+    Output (stdout):
+        the version number of `_package` in the local ports index
+
+    '
+    local _package _line _fqpn _n _lines
+    local _indexdir _indexfile
+
+    _package="$1"
+
+#    _val=$(pkg rquery -I "${_package}" | cut -f 1 -d '|')
+#    _rv=$?
+#    immediate_index_version=${_val##*-}
+    #    return ${_rv}
+
+    _indexdir="$(get_ports_index_directory)"
+    _indexfile="$(pkg config INDEXFILE)"
+
+    if [ -r "${_indexdir}/${_indexfile}" ] ; then
+        #
+        # Note: Direct piping does not set immediate_index_version at return correctly
+        #       "_line" is set correctly and parsing works, but the return 0 seems to kill
+        #       some of the previous effects.
+        #
+        # "grep" does a fast pre-selection, reading, parsing and comparing is done for
+        # exact matching.
+        #
+        # Use BREs because a `+' char needs to be handled as an ordinary
+        # character (e.g. for the "lucene++" package).
+        # Note that `^' at the start of an RE is not an ordinary
+        # character. See re_format(7).
+        #
+        _lines=$(/usr/bin/grep -G '^'"${_package}" "${_indexdir}/${_indexfile}")
+        while read _line ; do
+            _fqpn="${_line%%|*}"
+            _n=${_fqpn%-*}
+	    if [ "${_package}" = "${_n}" ] ; then
+                printf '%s' "${_fqpn##*-}"
+                return 0
+            fi
+        done <<EOF1334TGH1
+${_lines}
+EOF1334TGH1
+    fi
+    return 1
+}
+
+
+get_mapping() {
+    : 'Determine whether a package `_package` is essentially the same as
+    another package.
+
+    Args:
+        _package: the new name of the package
+
+    Returns:
+        0 when a package mapping has been found, 1 otherwise
+
+    Output (stdout):
+        the name of the package on which `_package` is based on
+
+    This command reads from the mapping database in in file
+    `/usr/local/etc/local-bsdtools/package-mapping.conf`.
+    Example::
+
+        #
+        # _package                         mapped_package_name
+        #
+        fmg-nextcloud-php71                nextcloud-php71
+        fmg-nextcloud-twofactor_totp-php71 nextcloud-twofactor_totp-php71
+
+    '
+    local _package _n _mapped
+
+    _package="$1"
+
+    if [ -r "${PACKAGE_MAPPING}" ] ; then
+        while read _n _mapped ; do
+            if [ "${_n}" = "${_package}" ] ; then
+                printf '%s' "${_mapped}"
+                return 0
+            fi
+        done < ${PACKAGE_MAPPING}
+    fi
+    return 1
+}
+
+
+print_title() {
+    : 'Print the output title line for a package
+
+    Args:
+        _package: the package name
+        _version: the package version
+        _repo:    the repository name
+
+    Input (Globals).
+        title_printed: a global that determines if the title really needs
+                       to be printed.
+
+                       If it is an empty string the the title is
+                       really printed and the variable is set to
+                       "yes".
+
+    Output (Globals):
+        title_printed: set to "yes" if the title has been printed
+
+    '
+    local _package _version _repo
+
+    _package="$1"
+    _version="$2"
+    _repo="$3"
+    if [ -z "${title_printed}" ]; then
+        printf '%-36s %-17s (%s)\n' "${_package}" "${_version}" "${_repo}"
+        title_printed=yes
+    fi
+}
+
+
+print_detail_item() {
+    : 'Print a detail item to stdout.
+
+    The description `_descr` will not be printed if the label `_label`
+    is ``?``.
+
+    Args:
+        _repo: the repository name
+        _version: the version number to print to
+        _label: the label (aka comparison character) to print to
+        _descr: the description to print to
+        _indent: (optional) extra indentation number (default 0)
+
+    Output (stdout):
+        the formatted detail line
+
+    '
+    local _repo _version _label _descr _indent
+    local _real_descr
+
+    _repo="$1"
+    _version="$2"
+    _label="$3"
+    _descr="$4"
+    _indent="$5"
+
+    if [ -z "${_indent}" ]; then
+        _indent="0"
+    fi
+    if [ "${_label}" = '?' ]; then
+        _real_descr=''
+    else
+        _real_descr="${_descr}"
+    fi
+
+    printf '%-*s  %-15s: %-17s %s %s\n' $((_indent)) '' "${_repo}" "${_version}" "${_label}" "${_real_descr}"
+}
+
+
+check_ports() {
+    : 'Implementation of all command variants besides of `-n`
+
+    '
+    local _ipackage _iversion _irepo _mapped_package_name _dummy
+    local _print_detail _local_index_exists
+    local _index_version _index_label _index_descr
+    local _remote_version_FreeBSD _remote_label_FreeBSD _remote_descr_FreeBSD
+    local _remote_version_LocalBSDPorts _remote_label_LocalBSDPorts _remote_descr_LocalBSDPorts
+    local _remote_version_SharedLocalRepo _remote_label_SharedLocalRepo _remote_descr_SharedLocalRepo
+    local _remote_version_LocalRepo _remote_label_LocalRepo _remote_descr_LocalRepo
+
+    if test_exists_local_index; then
+        _local_index_exists="1"
+    fi
+    pkg query '%n %v %R' |
+        while read -r _ipackage _iversion _irepo; do
+            title_printed=""
+            _print_detail=""
+            if [ -n "${_local_index_exists}" ]; then
+                read -r _dummy _index_label _index_descr <<EOF_INDEX
+$(pkg version -U -I -n "${_ipackage}" -v)
+EOF_INDEX
+            fi
+            read -r _dummy  _remote_label_FreeBSD _remote_descr_FreeBSD <<EOF_FreeBSD
+$(pkg version -U -R -r "${FREEBSD_REPO}" -n "${_ipackage}" -v)
+EOF_FreeBSD
+            read -r _dummy _remote_label_LocalBSDPorts _remote_descr_LocalBSDPorts <<EOF_LocalBSDPorts
+$(pkg version -U -R -r "${LOCALBSDPORTS_REPO}" -n "${_ipackage}" -v)
+EOF_LocalBSDPorts
+            read -r _remote_fpname_SharedLocalRepo _remote_label_SharedLocalRepo _remote_descr_SharedLocalRepo <<EOF_SharedLocalRepo
+$(pkg version -U -R -r "${SHARED_LOCAL_REPO}" -n "${_ipackage}" -v)
+EOF_SharedLocalRepo
+            read -r _remote_fpname_LocalRepo _remote_label_LocalRepo _remote_descr_LocalRepo <<EOF_LocalRepo
+$(pkg version -U -R -r "${LOCAL_REPO}" -n "${_ipackage}" -v)
+EOF_LocalRepo
+
+            if [ -n "${option_verbose}" ]; then
+                print_title "${_ipackage}" "${_iversion}" "${_irepo}"
+            fi
+            if get_mapping "${_ipackage}" >/dev/null;  then
+                _print_detail="1"
+            fi
+            if [ -n "${option_alldata}" ]; then
+                _print_detail="1"
+            else
+                if [ -n "${option_short}" ]; then
+                    case "${_irepo}" in
+                        "${FREEBSD_REPO}")
+                            if [  -n "${_local_index_exists}" ]; then
+                                if [ "${_index_label}" != '<' -a "${_index_label}" != '=' ]; then
+                                    _print_detail=1
+                                fi
+                            fi
+                            if [ "${_remote_label_FreeBSD}" != '=' -o "${_remote_label_SharedLocalRepo}" != '?' -o "${_remote_label_LocalRepo}" != '?' -o "${_remote_label_LocalBSDPorts}" != '?' ]; then
+                                _print_detail=1
+                            fi
+                            ;;
+                        "${LOCALBSDPORTS_REPO}")
+                            if [  -n "${_local_index_exists}" ]; then
+                                if [ "${_index_label}" != '=' ]; then
+                                    _print_detail=1
+                                fi
+                            fi
+                            if [ "${_remote_label_FreeBSD}" != '>' -o "${_remote_label_LocalRepo}" != '?' -o "${_remote_label_SharedLocalRepo}" != '?' -o "${_remote_label_LocalBSDPorts}" = '?' -o "${_remote_label_LocalBSDPorts}" = '<' ]; then
+                                _print_detail=1
+                            fi
+                            ;;
+                        "${SHARED_LOCAL_REPO}")
+                            _print_detail=1
+                            ;;
+                        "${LOCAL_REPO}")
+                            _print_detail=1
+                            ;;
+                        "${PORTS_DIRECT_INSTALLED_REPO}")
+                            _print_detail=1
+                            ;;
+                        *)
+                            echo "ERROR: unhandled repository: ${_irepo}" >&2
+                            exit 1
+                            ;;
+                    esac
+                else
+                    if [ -n "${_local_index_exists}" ]; then
+                        if [ "${_index_label}" != '?' -a "${_index_label}" != '=' ]; then
+                            _print_detail=1
+                        fi
+                    fi
+                    if [ "${_remote_label_FreeBSD}" != '?' -a "${_remote_label_FreeBSD}" != '=' ]; then
+                        _print_detail=1
+                    fi
+                    if [ "${_remote_label_LocalBSDPorts}" != '?' -a "${_remote_label_LocalBSDPorts}" != '=' ]; then
+                        _print_detail=1
+                    fi
+                    if [ "${_remote_label_SharedLocalRepo}" != '?' -a "${_remote_label_SharedLocalRepo}" != '=' ]; then
+                        _print_detail=1
+                    fi
+                    if [ "${_remote_label_LocalRepo}" != '?' -a "${_remote_label_LocalRepo}" != '=' ]; then
+                        _print_detail=1
+                    fi
+                fi
+            fi
+            if [ -n "${_print_detail}" ]; then
+                print_title "${_ipackage}" "${_iversion}" "${_irepo}"
+                if [ -n "${_local_index_exists}" ]; then
+                    _index_version="$(get_ports_index_version "${_ipackage}")"
+                    print_detail_item "INDEX" "${_index_version}" "${_index_label}" "${_index_descr}"
+                fi
+                if [ -n "${option_alldata_FreeBSD}" -o "${_remote_label_FreeBSD}" != '?' ]; then
+                    _remote_version_FreeBSD="$(pkg rquery -U -r "${FREEBSD_REPO}" '%v' "${_ipackage}")"
+                    print_detail_item "${FREEBSD_REPO}" "${_remote_version_FreeBSD}" "${_remote_label_FreeBSD}" "${_remote_descr_FreeBSD}"
+                fi
+                if [ -n "${option_alldata_LocalBSDPorts}" -o "${_remote_label_LocalBSDPorts}" != '?' ]; then
+                    _remote_version_LocalBSDPorts="$(pkg rquery -U -r "${LOCALBSDPORTS_REPO}" '%v' "${_ipackage}")"
+                    print_detail_item "${LOCALBSDPORTS_REPO}" "${_remote_version_LocalBSDPorts}" "${_remote_label_LocalBSDPorts}" "${_remote_descr_LocalBSDPorts}"
+                fi
+                if [ -n "${option_alldata_SharedLocalRepo}" -o "${_remote_label_SharedLocalRepo}" != '?' ]; then
+                    _remote_version_SharedLocalRepo="$(pkg rquery -U -r "${SHARED_LOCAL_REPO}" '%v' "${_ipackage}")"
+                    print_detail_item "${SHARED_LOCAL_REPO}" "${_remote_version_SharedLocalRepo}" "${_remote_label_SharedLocalRepo}" "${_remote_descr_SharedLocalRepo}"
+                fi
+                if [ -n "${option_alldata_LocalRepo}" -o "${_remote_label_LocalRepo}" != '?' ]; then
+                    _remote_version_LocalRepo="$(pkg rquery -U -r "${LOCAL_REPO}" '%v' "${_ipackage}")"
+                    print_detail_item "${LOCAL_REPO}" "${_remote_version_LocalRepo}" "${_remote_label_LocalRepo}" "${_remote_descr_LocalRepo}"
+                fi
+                _mapped_package_name="$(get_mapping "${_ipackage}")"
+                if [ -n "${_mapped_package_name}" ] ; then
+	            printf '%18s %s %s (%s)\n' "--------------->" "${_mapped_package_name}" "$(pkg rquery -U '%v' "${_mapped_package_name}")" "$(pkg rquery -U '%R' "${_mapped_package_name}")"
+                    if [ -n "${_local_index_exists}" ]; then
+                        print_detail_item "INDEX" "$(get_ports_index_version "${_mapped_package_name}")" "" ""
+                    fi
+                    print_detail_item "${FREEBSD_REPO}" "$(pkg rquery -U -r "${FREEBSD_REPO}" '%v' "${_mapped_package_name}")" "" ""
+                    print_detail_item "${LOCALBSDPORTS_REPO}" "$(pkg rquery -U -r "${LOCALBSDPORTS_REPO}" '%v' "${_mapped_package_name}")" "" ""
+                    print_detail_item "${SHARED_LOCAL_REPO}" "$(pkg rquery -U -r "${SHARED_LOCAL_REPO}" '%v' "${_mapped_package_name}")" "" ""
+                    print_detail_item "${LOCAL_REPO}" "$(pkg rquery -U -r "${LOCAL_REPO}" '%v' "${_mapped_package_name}")" "" ""
+                fi
+            fi
+        done
+}
+
+
+check_given_packages() {
+    : 'Check the status of all given packages in the most detail possible
+
+    Args:
+        $@: the name of packaged to handle to
+
+    '
+    local _package _version _label _repo _descr _dummy
+    local _local_index_exists _mapped_package_name
+
+    if test_exists_local_index; then
+        _local_index_exists="1"
+    fi
+
+    while [ $# -gt 0 ]; do
+        _package="$1"
+        shift
+        read -r _version _repo <<EOF_INSTALLED
+$(pkg query '%v %R' "${_package}")
+EOF_INSTALLED
+        if [ -n "${_version}" ]; then
+            title_printed=""
+            print_title "${_package}" "${_version}" "${_repo}"
+            if [ -n "${_local_index_exists}" ]; then
+                read -r _dummy _label _descr <<EOF_INDEX
+$(pkg version -U -I -n "${_package}" -v)
+EOF_INDEX
+                _version="$(get_ports_index_version "${_package}")"
+                print_detail_item "INDEX" "${_version}" "${_label}" "${_descr}"
+            fi
+            for _repo in "${FREEBSD_REPO}" "${LOCALBSDPORTS_REPO}" "${SHARED_LOCAL_REPO}" "${LOCAL_REPO}"; do
+                read -r _dummy  _label _descr <<EOF_REPO
+$(pkg version -U -R -r "${_repo}" -n "${_package}" -v)
+EOF_REPO
+                _version="$(pkg rquery -U -r "${_repo}" '%v' "${_package}")"
+                print_detail_item "${_repo}" "${_version}" "${_label}" "${_descr}"
+            done
+            _mapped_package_name="$(get_mapping "${_ipackage}")"
+            if [ -n "${_mapped_package_name}" ] ; then
+                printf '%18s %s %s (%s)\n' "--------------->" "${_mapped_package_name}" "$(pkg rquery -U '%v' "${_mapped_package_name}")" "$(pkg rquery -U '%R' "${_mapped_package_name}")"
+                if [ -n "${_local_index_exists}" ]; then
+                    print_detail_item "INDEX" "$(get_ports_index_version "${_mapped_package_name}")" "" ""
+                fi
+                for _repo in "${FREEBSD_REPO}" "${LOCALBSDPORTS_REPO}" "${SHARED_LOCAL_REPO}" "${LOCAL_REPO}"; do
+                    print_detail_item "${_repo}" "$(pkg rquery -U -r "${_repo}" '%v' "${_mapped_package_name}")" "" ""
+                done
+            fi
+        fi
+    done
+}
+
+
+option_alldata=""
+option_alldata_FreeBSD=""
+option_alldata_LocalBSDPorts=""
+opeion_alldata_SharedLocalRepo=""
+option_alldata_LocalRepo=""
+option_short=""
+option_verbose=""
+option_packages=""
+
+while getopts "VhAansv" _opt ; do
+    case ${_opt} in
+	V)
+            printf 'check-ports %s\n' '@@SIMPLEVERSIONSTR@@'
+            exit 0
+	    ;;
+        h)
+            echo "${USAGE}"
+            exit 0
+            ;;
+        A)
+            # Print for every package the status of all repositories
+            option_alldata="1"
+            option_alldata_FreeBSD="1"
+            option_alldata_LocalBSDPorts="1"
+            option_alldata_SharedLocalRepo="1"
+            option_alldata_LocalRepo="1"
+            ;;
+        a)
+            # Print the data of all repos that have the package
+            option_alldata="1"
+            ;;
+        n)
+            #
+            # Print status of given packages in all details.
+            # No other options are respected.
+            #
+            option_packages="1"
+            ;;
+        s)
+            # "short" output: if installed from FreeBSD repo: don't
+            # report if only the index is newer
+            option_short="1"
+            ;;
+        v)
+            #
+            # Print all titles and repo of every installed package always.
+            # The output of the other repo status nevertheless depends on the
+            # other flag settings.
+            #
+            option_verbose="1"
+            ;;
+        \?)
+            exit 2
+            ;;
+        *)
+            echo "option handling failed" >&2
+            exit 2
+            ;;
+    esac
+done
+
+#
+# Reset the Shell's option handling system to prepare for handling
+# command-local options.
+#
+shift $((OPTIND-1))
+OPTIND=1
+
+if [ -n "${option_short}" -a -n "${option_alldata}" ]; then
+    echo "the -s option cannot be combined with -A or -a" >&2
+    exit 2
+fi
+
+if [ -n "${option_packages}" ]; then
+    check_given_packages "$@"
+else
+    check_ports
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/fjail	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,710 @@
+#!/bin/sh
+# -*- indent-tabs-mode: nil; -*-
+: 'A very minimal BSD Jail management tool.
+
+:Author:    Franz Glasner
+:Copyright: (c) 2019-2024 Franz Glasner.
+            All rights reserved.
+:License:   BSD 3-Clause "New" or "Revised" License.
+            See LICENSE for details.
+            If you cannot find LICENSE see
+            <https://opensource.org/licenses/BSD-3-Clause>
+:ID:        @(#)@@SIMPLEVERSIONTAG@@
+
+'
+
+set -eu
+
+VERSION="@@VERSION@@"
+
+USAGE='
+USAGE: fjail [ OPTIONS ] COMMAND [ COMMAND OPTIONS ] [ ARG ... ]
+
+OPTIONS:
+
+  -V    Print the program name and version number to stdout and exit
+
+  -h    Print this help message to stdout and exit
+
+COMMANDS:
+
+  datasets [OPTIONS] PARENT CHILD
+
+    Create ZFS datasets to be used within a jail
+
+    PARENT must exist already and CHILD must not exist.
+
+    -A        Set "canmount=noauto" for datasets
+    -o        Do not create var/empty as read-only dataset but with normal settings
+    -s        Also create a dataset for freebsd-update data files
+    -t        Create a more tiny set of datasets
+    -T        Create only an extra tiny set of datasets
+    -u        Do not automatically mount newly created datasets
+
+  mount
+
+    See sibling tool `fzfs'"'"'
+
+  umount
+
+    See sibling tool `fzfs'"'"'
+
+  privs MOUNTPOINT
+
+    Adjust some Unix privileges to mounted jail datasets
+
+  populate MOUNTPOINT BASETXZ
+
+    Populate the jail directory in MOUNTPOINT with the base system in BASETXZ
+
+  configure [OPTIONS] MOUNTPOINT
+
+    Configure some basic parts of the system at MOUNTPOINT:
+    disable root password, syslog and other basic configuration settings
+
+    Also handle thin jails by checking whether "etc" is a symlink to
+    "skeleton/etc".
+
+    -d        Temporarily mount a devfs filesystem to MOUNTPOINT/dev
+
+   hostid
+
+     Print proposals for a hostuuid and hostid
+
+  copy [OPTIONS] SOURCE-DATASET DEST-DATASET
+
+    Copy a tree of ZFS datasets with "zfs send -R" and "zfs receive".
+    Note that the destination dataset must not exist already.
+
+    -r        Copy the datasets with the -Lec options (aka "raw")
+    -u        Do not automatically mount received datasets
+
+  freebsd-update [OPTIONS] DIRECTORY OPERATIONS...
+
+    -c CURRENTLY-RUNNING   Assume the systen given in CURRENTLY-RUNNING is
+                           installed/running at given DIRECTORY
+
+ENVIRONMENT:
+
+  All environment variables that affect "zfs" are effective also.
+
+DESCRIPTION:
+
+  All commands with the exception of "populate" require ZFS as
+  filesystem.
+'
+
+
+_p_datadir="$(dirname "$0")"/../share/local-bsdtools
+. "${_p_datadir}/common.subr"
+
+
+# Reset to standard umask
+umask 0022
+
+
+#:
+#: Check whether a FreeBSD version at a given location matches the userland
+#: version of the host where the current process run.
+#:
+#: Args:
+#:   $1: the location where to check for
+#:   $2: an optional reference FreeBSD version to compare to (default is the
+#:       version of the host)
+#:
+#: Returns:
+#:   0: if the userland versions match, 1 otherwise
+#:
+#: Exit:
+#:   1: on fatal errors (e.g. /bin/freebsd-version not found or errors)
+#:
+_has_same_userland_version() {
+    local directory ref_version
+
+    local _directory_version
+
+    directory="$1"
+    ref_version="${2:-}"
+
+    if [ -z "${ref_version}" ]; then
+        ref_version=$(/bin/freebsd-version -u) || exit 1
+    fi
+    _directory_version=$(chroot -- "${directory}" /bin/freebsd-version -u) || exit 1
+    if [ "${ref_version%%-*}" = "${_directory_version%%-*}" ]; then
+        return 0
+    fi
+    return 1
+}
+
+
+#
+# "datasets" -- create the ZFS dataset tree
+#
+# command_datasets [ -u ] parent-dataset child-dataset
+#
+#    -u  do not automatically mount newly created datasets
+#
+command_datasets() {
+    # parent ZFS dataset -- child ZFS dataset name
+    local _pds _cds
+    # and its mount point
+    local _pmp _get
+    # full name of the dataset
+    local _ds
+    # dynamic ZFS options  -- create cache for freebsd-update  -- use a more tiny layout
+    local _zfsopts _fbsdupdate _tiny _zfsnoauto _varempty_ro
+
+    _zfsopts=""
+    _fbsdupdate=""
+    _tiny="no"
+    _zfsnoauto=""
+    _varempty_ro="-o readonly=on"
+    while getopts "oustAT" _opt ; do
+        case ${_opt} in
+            A)
+                #
+                # set canmount=noauto where otherwise canmount=on would have been set
+                # or inherited
+                #
+                _zfsnoauto="-o canmount=noauto"
+                ;;
+            o)
+                # Clear out the default setting of creating var/empty as read-only dataset
+                _varempty_ro=""
+                ;;
+            t)
+                # use a more tiny layout
+                _tiny="yes"
+                ;;
+            T)  # extra tiny layout
+                _tiny="extra"
+                ;;
+            u)
+                # do not mount newly created datasets
+                _zfsopts="${_zfsopts} -u"
+                ;;
+            s)
+                # create also a dataset for freebsd-update data
+                _fbsdupdate="yes"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    _pds="$1"
+    if [ -z "${_pds}" ]; then
+        echo "ERROR: no parent dataset given" >&2
+        return 2
+    fi
+    _pmp=$(zfs list -H -o mountpoint -t filesystem "${_pds}" 2>/dev/null) || { echo "ERROR: dataset \`${_pds}' does not exist" >&2; return 1; }
+    case "${_pmp}" in
+        none)
+            echo "ERROR: dataset \`${_pds}' has no mountpoint" >&2
+            return 1
+            ;;
+        legacy)
+            echo "ERROR: dataset \`${_pds}' has a \`${_mp}' mountpoint" >&2
+            return 1
+            ;;
+        *)
+            # VOID
+            ;;
+    esac
+    _cds="$2"
+    if [ -z "${_cds}" ]; then
+        echo "ERROR: no child dataset given" >&2
+        return 2
+    fi
+    _ds="${_pds}/${_cds}"
+    echo "Resulting new root dataset is \`${_ds}' at mountpoint \`${_pmp}/${_cds}'"
+    if zfs list -H -o mountpoint -t filesystem "${_ds}" >/dev/null 2>/dev/null; then
+        echo "ERROR: dataset \`${_ds}' does already exist" >&2
+        return 1
+    fi
+
+    #
+    # NOTE: For BEs these directory will be *excluded* from the BE
+    #
+    #   /tmp
+    #   /usr/home
+    #   /usr/ports
+    #   /usr/src
+    #   /var/audit
+    #   /var/crash
+    #   /var/log
+    #   /var/mail
+    #   /var/tmp
+    #
+    zfs create ${_zfsopts} ${_zfsnoauto} -o atime=off                                                        "${_ds}"
+    zfs create ${_zfsopts} ${_zfsnoauto} -o sync=disabled -o setuid=off                                      "${_ds}/tmp"
+    if [ "${_tiny}" != "extra" ]; then
+        if [ "${_tiny}" = "yes" ]; then
+            zfs create ${_zfsopts} -o canmount=off                                                           "${_ds}/usr"
+        else
+            zfs create ${_zfsopts} ${_zfsnoauto}                                                             "${_ds}/usr"
+        fi
+        zfs create ${_zfsopts} ${_zfsnoauto} -o setuid=off                                                   "${_ds}/usr/home"
+        zfs create ${_zfsopts} ${_zfsnoauto}                                                                 "${_ds}/usr/local"
+    fi
+    if [ \( "${_tiny}" = "yes" \) -o \( "${_tiny}" = "extra" \) ]; then
+        zfs create ${_zfsopts} -o canmount=off                                                               "${_ds}/var"
+    else
+        zfs create ${_zfsopts} ${_zfsnoauto}                                                                 "${_ds}/var"
+    fi
+    if [ "${_tiny}" != "extra" ]; then
+        zfs create ${_zfsopts} ${_zfsnoauto} -o exec=off -o setuid=off                                                      "${_ds}/var/audit"
+        zfs create ${_zfsopts} ${_zfsnoauto} -o exec=off -o setuid=off                                                      "${_ds}/var/cache"
+        zfs create ${_zfsopts} ${_zfsnoauto} -o exec=off -o setuid=off -o primarycache=metadata -o compression=off          "${_ds}/var/cache/pkg"
+        zfs create ${_zfsopts} ${_zfsnoauto} -o exec=off -o setuid=off -o compression=off                                   "${_ds}/var/crash"
+    fi
+    if [ "$_fbsdupdate" = "yes" ]; then
+        if [ \( "${_tiny}" = "yes" \) -o \( "${_tiny}" = "extra" \) ]; then
+            zfs create ${_zfsopts} -o canmount=off -o exec=off -o setuid=off                                 "${_ds}/var/db"
+        else
+            zfs create ${_zfsopts} ${_zfsnoauto} -o exec=off -o setuid=off                                   "${_ds}/var/db"
+        fi
+        zfs create ${_zfsopts} ${_zfsnoauto} -o exec=off -o setuid=off -o primarycache=metadata -o compression=off          "${_ds}/var/db/freebsd-update"
+    fi
+    zfs create ${_zfsopts} ${_zfsnoauto} ${_varempty_ro} -o exec=off -o setuid=off                                          "${_ds}/var/empty"
+    zfs create ${_zfsopts} ${_zfsnoauto} -o exec=off -o setuid=off -o primarycache=metadata                                 "${_ds}/var/log"
+    zfs create ${_zfsopts} ${_zfsnoauto} -o exec=off -o setuid=off -o atime=on                                              "${_ds}/var/mail"
+    zfs create ${_zfsopts} ${_zfsnoauto} -o sync=disabled -o exec=off -o setuid=off -o compression=off -o primarycache=all  "${_ds}/var/run"
+    zfs create ${_zfsopts} ${_zfsnoauto} -o sync=disabled -o setuid=off                                                     "${_ds}/var/tmp"
+}
+
+
+#
+# "populate" -- populate the datasets with content from a FreeBSD base.txz
+#
+# command_populate mountpoint basetxz
+#
+command_populate() {
+    # MOUNTPOINT -- base.txz
+    local _mp _basetxz
+
+    _mp="$1"
+    _basetxz="$2"
+
+    if [ -z "${_mp}" ]; then
+        echo "ERROR: no mountpoint given" >&2
+        return 2
+    fi
+    if [ -z "${_basetxz}" ]; then
+        echo "ERROR: no base.txz given" >&2
+        return 2
+    fi
+    if [ ! -d "${_mp}" ]; then
+        echo "ERROR: mountpoint \`${_mp}' does not exist" >&2
+        return 1
+    fi
+    if [ ! -r "${_basetxz}" ]; then
+        echo "ERROR: file \`${_basetxz}' is not readable" >&2
+        return 1
+    fi
+
+    #
+    # Handle /var/empty separately later: could be already there and
+    # mounted read-only.
+    #
+    tar -C "${_mp}" --exclude=./var/empty -xJp -f "${_basetxz}" || { echo "ERROR: tar encountered errors" >&2; return 1; }
+    if [ -d "${_mp}/var/empty" ]; then
+        #
+        # If /var/empty exists already try to extract with changing the
+        # flags (e.g. `schg'). But be ignore errors here.
+        #
+        tar -C "${_mp}" -xJp -f "${_basetxz}" ./var/empty || { echo "tar warnings for handling ./var/empty ignored because ./var/empty exists already" >&2; }
+    else
+        # Just extract /var/empty normally
+        tar -C "${_mp}" -xJp -f "${_basetxz}" ./var/empty || { echo "ERROR: tar encountered errors" >&2; return 1; }
+    fi
+
+    find "${_mp}/boot" -type f -delete
+}
+
+
+#
+# "hostid" -- print a proposal for hostid/hostuuid settings in a jail
+#
+# command_hostid
+#
+command_hostid() {
+    #
+    # hostid and hostuuid should be set (at least for consistency ressons)
+    # in vnet jails (see /etc/rc.d/hostid and /etc/rc.d/hostid_save).
+    # They can be set in the jail.conf.
+    # Print one here that can be pasted into the jail.conf if needed.
+    #
+    # hostid and hostuuid for non-vnet jails are inherited from the parent/host.
+    #
+    # See also /etc/rc.d/hostid and /etc/rc.d/hostid_save.
+    #
+    local _new_hostuuid _new_hostid
+    _new_hostuuid="$(uuidgen)"
+    _new_hostid="$(echo -n ${_new_hostuuid} | /sbin/md5)"
+    _new_hostid="0x${_new_hostid%%????????????????????????}"
+
+    echo "Proposed hostuuid/hostid:"
+    echo "  host.hostuuid = \"${_new_hostuuid}\";"
+    echo "  host.hostid = $((_new_hostid));"
+    #echo "  host.hostid = ${_new_hostid};"
+}
+
+
+#
+# "configure" -- configure the mountpoint
+#
+# command_configure mountpoint
+#
+command_configure() {
+    # mountpoint
+    local _mp
+    local _opt_devfs
+
+    local _pcl _umount_devfs
+
+    _umount_devfs=""
+
+    _opt_devfs=""
+    while getopts "d" _opt ; do
+        case ${_opt} in
+            d)
+                _opt_devfs="yes"
+                ;;
+            \?)
+                return 2;
+                ;;
+            *)
+                echo "ERROR: option handling failed" 1>&2
+                return 2
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    _mp="$1"
+
+    if [ -z "${_mp}" ]; then
+        echo "ERROR: no mountpoint given" >&2
+        return 2
+    fi
+    if [ ! -d "${_mp}" ]; then
+        echo "ERROR: mountpoint \`${_mp}' does not exist" >&2
+        return 1
+    fi
+
+    if [ -c "${_mp}/dev/null" ]; then
+        if [ "${_opt_devfs}" = "yes" ]; then
+            echo "WARNING: devfs is already mounted - mounting skipped"
+        fi
+    else
+        if [ "${_opt_devfs}" = "yes" ]; then
+            echo "Mounting devfs"
+            mount -t devfs devfs "${_mp}/dev"
+            _umount_devfs="yes"
+        else
+            echo "ERROR: a working devfs is needed at \`{_mp}/dev' (use \`-d')" >&2
+            return 1
+        fi
+    fi
+
+    # Deactive the by default empty root password
+    pw -R "${_mp}" usermod -w no -n root
+
+    if [ -f "${_mp}/etc/defaults/rc.conf" ]; then
+
+        sysrc -R "${_mp}" sendmail_enable=NONE
+        sysrc -R "${_mp}" clear_tmp_enable=YES
+        sysrc -R "${_mp}" clear_tmp_X=NO
+        sysrc -R "${_mp}" syslogd_flags=-ss
+        sysrc -R "${_mp}" bsdstats_enable=NO       # no automatic BSD stats when booting (for periodic see below)
+    else
+        echo "WARNING: No \"${_mp}/etc/defaults/rc.conf\": not configuring \"rc.conf\""
+    fi
+
+    if [ -f "${_mp}/usr/share/zoneinfo/Europe/Berlin" ]; then
+        # Timezone to CET
+        if [ ! -f "${_mp}/etc/localtime" ]; then
+            echo "Setting timezone to Europe/Berlin"
+            # Handle thin jails automatically (but check expectations very strictly)
+            if [ \( -L "${_mp}/etc" \) -a \( "$(readlink "${_mp}/etc")" = "skeleton/etc" \) ]; then
+                ln -s ../../usr/share/zoneinfo/Europe/Berlin "${_mp}/etc/localtime"
+            else
+                ln -s ../usr/share/zoneinfo/Europe/Berlin "${_mp}/etc/localtime"
+            fi
+            echo "Europe/Berlin" > "${_mp}/var/db/zoneinfo"
+        else
+            echo "WARNING: \"${_mp}/etc/localtime\" exists already -- not changed"
+        fi
+    else
+        echo "WARNING: No timezone data file found at \"${_mp}/usr/share/zoneinfo/Europe/Berlin\": skipping timezone setup"
+    fi
+
+    # resolv.conf
+    if [ ! -f "${_mp}/etc/resolv.conf" ]; then
+        echo "Copying the host's resolv.conf into the jail"
+        cp -p /etc/resolv.conf "${_mp}/etc/resolv.conf"
+    else
+        echo "WARNING: \"${_mp}/etc/resolv.conf\" exists already -- not changed"
+    fi
+
+    # Call newaliases within the jail
+    echo "Calling \"newaliases\""
+    chroot -- "${_mp}" /usr/bin/newaliases
+
+    _pcl="${_mp}/etc/periodic.conf.local"
+    if [ ! -f "${_pcl}" ]; then
+        echo "Adjusting periodic.conf.local"
+        echo "Periodic script log into files ..."
+        echo "daily_output=\"/var/log/daily.log\"" > "${_pcl}"
+        echo "weekly_output=\"/var/log/weekly.log\"" >> "${_pcl}"
+        echo "monthly_output=\"/var/log/monthly.log\"" >> "${_pcl}"
+        echo "daily_status_security_output=\"/var/log/security\"" >> "${_pcl}"
+        echo "weekly_status_security_output=\"/var/log/security\"" >> "${_pcl}"
+        echo "monthly_status_security_output=\"/var/log/security\"" >> "${_pcl}"
+
+        echo "security_status_chkmounts_enable=\"NO\"" >> "${_pcl}"
+
+        echo "Disable some scripts that are enabled by default ..."
+        echo "daily_ntpd_leapfile_enable=\"NO\"" >> "${_pcl}"
+        echo "daily_status_zfs_zpool_list_enable=\"NO\"" >> "${_pcl}"
+        echo "daily_status_disks_enable=\"NO\"" >> "${_pcl}"
+        echo "daily_status_uptime_enable=\"NO\"" >> "${_pcl}"
+
+        #
+        # bsdstats
+        #
+        echo "" >> "${_pcl}"
+        echo "#" >> "${_pcl}"
+        echo "# bsdstats" >> "${_pcl}"
+        echo "#" >> "${_pcl}"
+        # Disabled by default but make it more explicit
+        echo "monthly_statistics_enable=\"NO\"" >> "${_pcl}"
+        # If enabled: because we are in a jail there are no devices
+        echo "monthly_statistics_report_devices=\"NO\"" >> "${_pcl}"
+        # If enabled: report ports
+        echo "monthly_statistics_report_ports=\"YES\"" >> "${_pcl}"
+
+        echo "Creating system logfiles that are marked for automatic creation ..."
+        chroot -- "${_mp}" /usr/sbin/newsyslog -CN
+
+    else
+        echo "WARNING: \"${_pcl}\" exists already -- not changed"
+    fi
+
+    command_hostid
+
+    if [ "${_umount_devfs}" = "yes" ]; then
+        echo "Unmounting devfs"
+        umount "${_mp}/dev"
+    fi
+}
+
+
+#
+# "copy" -- ZFS copy of datasets
+#
+# command_copy source-dataset destination-dataset
+#
+command_copy() {
+    # source dataset -- destination dataset
+    local _source _dest
+    # dynamic ZFS options -- ZFS copy options
+    local _zfsopts _zfscopyopts
+
+    _zfsopts=""
+    _zfscopyopts=""
+    while getopts "ru" _opt ; do
+        case ${_opt} in
+            r)
+                # Use raw datasets
+                _zfscopyopts="-Lec"
+                ;;
+            u)
+                # do not mount newly created datasets
+                _zfsopts="${_zfsopts} -u"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    _source="$1"
+    if [ -z "${_source}" ]; then
+        echo "ERROR: no source dataset given" >&2
+        return 2
+    fi
+    _dest="$2"
+    if [ -z "${_dest}" ]; then
+        echo "ERROR: no source dataset given" >&2
+        return 2
+    fi
+    zfs send -R ${_zfscopyopts} -n -v "${_source}" || { echo "ERROR: ZFS operation failed in no-op mode" >&2; return 1; }
+    zfs send -R ${_zfscopyopts} "${_source}" | zfs receive ${_zfsopts} "${_dest}"  || { echo "ERROR: ZFS operation failed" >&2; return 1; }
+}
+
+
+#
+# "privs" -- adjust privileges
+#
+# To be used when all ZFS datasets are mounted.
+#
+command_privs() {
+    # mountpoint
+    local _mp _d _veds _get _vestatus
+
+    _mp="$1"
+    if [ -z "${_mp}" ]; then
+        echo "ERROR: no mountpoint given" >&2
+        return 2
+    fi
+    if [ ! -d "${_mp}" ]; then
+        echo "ERROR: directory \`${_mp}' does not exist" >&2
+        return 1
+    fi
+    for _d in tmp var/tmp ; do
+       chmod 01777 "${_mp}/${_d}"
+    done
+    chown root:mail "${_mp}/var/mail"
+    chmod 0775 "${_mp}/var/mail"
+
+    #
+    # Handle <mountpoint>/var/empty specially:
+    # make it writeable temporarily if it is mounted read-only:
+    #
+    _vestatus=""
+    _veds="$(_get_zfs_dataset_for_varempty "${_mp}")"
+    if [ $? -eq 0 ]; then
+        _vestatus=$(zfs list -H -o readonly -t filesystem ${_veds} 2>/dev/null) || { echo "ERROR: cannot determine readonly status of ${_mp}/var/empty" >&2; return 1; }
+        if [ "${_vestatus}" = "on" ]; then
+            zfs set readonly=off ${_veds} 1> /dev/null || { echo "ERROR: cannot reset readonly-status of ${_mp}/var/empty" >&2; return 1; }
+        fi
+    fi
+    # Set the access rights and the file flags as given in mtree
+    chmod 0555 "${_mp}/var/empty" || { echo "WARNING: Cannot chmod on var/empty" >&2; }
+    chflags schg "${_mp}/var/empty" || { echo "WARNING: Cannot chflags on var/empty" >&2; }
+    # Reset the read-only status of the mountpoint as it was before
+    if [ "${_vestatus}" = "on" ]; then
+        zfs set readonly=on ${_veds} 1> /dev/null || { echo "ERROR: cannot reactivate readonly-status of ${_mp}/var/empty" >&2; return 1; }
+    fi
+}
+
+
+#:
+#: Implement the "freebsd-update" command
+#:
+command_freebsd_update() {
+    local directory operations
+
+    local opt_currently_running
+
+    opt_currently_running=""
+    while getopts "c:" _opt ; do
+        case ${_opt} in
+            c)
+                opt_currently_running="$OPTARG"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    directory="${1-}"
+
+    [ -z "${directory}" ] && { echo "ERROR: no directory given" 1>&2; return 2; }
+    [ -d "${directory}" ] || { echo "ERROR: directory \`${directory}' does not exist" 1>&2; return 1; }
+
+    shift
+    operations="$@"
+
+    if _has_same_userland_version "${directory}" "${opt_currently_running}" ; then
+        if [ -n "${opt_currently_running}" ]; then
+            freebsd-update -b "${directory}" --currently-running "${opt_currently_running}" ${operations}
+        else
+            freebsd-update -b "${directory}" ${operations}
+        fi
+    else
+        echo "ERROR: Userland version mismatch" 1>&2
+        return 1
+    fi
+}
+
+
+#
+# Global option handling
+#
+while getopts "Vh" _opt ; do
+    case ${_opt} in
+        V)
+            printf 'fjail %s\n' '@@SIMPLEVERSIONSTR@@'
+            exit 0
+            ;;
+        h)
+            echo "${USAGE}"
+            exit 0
+            ;;
+        \?)
+            exit 2;
+            ;;
+        *)
+            echo "ERROR: option handling failed" >&2
+            exit 2
+            ;;
+    esac
+done
+
+#
+# Reset the Shell's option handling system to prepare for handling
+# command-local options.
+#
+shift $((OPTIND-1))
+OPTIND=1
+
+test $# -gt 0 || { echo "ERROR: no command given" >&2; exit 2; }
+
+command="$1"
+shift
+
+case "${command}" in
+    datasets)
+        command_datasets "$@"
+        ;;
+    mount)
+        exec "$(dirname $0)/fzfs" mount "$@"
+        ;;
+    umount|unmount)
+        exec "$(dirname $0)/fzfs" umount "$@"
+        ;;
+    privs)
+        command_privs "$@"
+        ;;
+    populate)
+        command_populate "$@"
+        ;;
+    configure)
+        command_configure "$@"
+        ;;
+    hostid)
+        command_hostid "$@"
+        ;;
+    copy)
+        command_copy "$@"
+        ;;
+    freebsd-update)
+        command_freebsd_update "$@"
+        ;;
+    *)
+        echo "ERROR: unknown command \`${command}'" >&2
+        exit 2
+        ;;
+esac
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/fpkg	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,384 @@
+#!/bin/sh
+# -*- indent-tabs-mode: nil; -*-
+: 'A pkg frontend for common operations that also operates in all
+running jails.
+
+:Author:    Franz Glasner
+:Copyright: (c) 2019-2024 Franz Glasner.
+            All rights reserved.
+:License:   BSD 3-Clause "New" or "Revised" License.
+            See LICENSE for details.
+            If you cannot find LICENSE see
+            <https://opensource.org/licenses/BSD-3-Clause>
+:ID:        @(#)@@SIMPLEVERSIONTAG@@
+
+'
+
+VERSION="@@VERSION@@"
+
+USAGE='
+USAGE: fpkg [ OPTIONS] COMMAND [ COMMAND-OPTIONS ]
+
+OPTIONS:
+
+  -V    Print the program name and version number to stdout and exit
+
+  -h    Print this help message to stdout and exit
+
+COMMANDS:
+
+  audit
+
+    `pkg audit` on the local host and all running visible and
+    compatible jails
+
+  update
+
+    `pkg update` on the local host and all running visible and
+    compatible jails
+
+  upgrade
+
+    `pkg upgrade` on the local host and all running visible and
+    compatible jails
+
+  check-upgrade
+  upgrade-check
+
+    `pkg upgrade -n` on the local host and all running visible and
+    compatible jails
+
+  check-fast-track
+    Check packages installed from the LocalBSDPorts repository against
+    the repositories `FreeBSD` and `LocalBSDPorts` on the local host
+    and all visible and compatible jails
+
+  config <name>
+
+    Retrieve the value of a given configuration option on the local host
+    and all running visible and compatible jails
+
+  uversion
+
+    Call `freebsd-version -u` on the local host and all running visible
+    and compatible jails
+
+  vv
+
+   `pkg -vv` on the local host and all running visible jails
+
+ENVIRONMENT:
+
+  FPKG_AUDIT_FLAGS
+                 Additional flags given to `pkg audit`
+                 (Default: -Fr)
+
+  FPKG_UPDATE_FLAGS
+                 Additional flags given to `pkg update`
+                 (Default: empty)
+
+  FPKG_UPGRADE_FLAGS
+                 Additional flags given to `pkg upgrade` and `pkg upgrade -n`
+                 (Default: empty)
+
+  FPKG_SIGN
+                 Marker for the begin of an output group (local host or jail)
+                 (Default: "===> ")
+
+  FPKG_SKIPSIGN
+                 Marker for the begin of a skipped output group
+                 (Default: "----> ")
+
+  All other environment variables that affect `pkg` are effective also.
+
+  A "compatible jail" is a jail that'"'"'s "freebsd-version -u" is the same
+  as the host'"'"'s.
+'
+
+#
+# Configuration directory
+#
+: ${CONFIGDIR:=@@ETCDIR@@}
+
+test -r "${CONFIGDIR}/pkgtools.conf" && . "${CONFIGDIR}/pkgtools.conf"
+
+: ${FPKG_AUDIT_FLAGS:=-Fr}
+: ${FPKG_UPDATE_FLAGS:=}
+: ${FPKG_UPGRADE_FLAGS:=}
+: ${FPKG_SIGN:='===> '}
+: ${FPKG_SKIPSIGN:='----> '}
+
+#
+# The official FreeBSD binary repository
+#
+: ${FREEBSD_REPO:=FreeBSD}
+
+#
+# Local repository with ports with default OPTIONS (i.e. unchanged)
+# but newer than the packages in the "FreeBSD" repository.
+# Some sort of a fast-track repository.
+#
+: ${LOCALBSDPORTS_REPO:=LocalBSDPorts}
+
+
+has_same_userland_version() {
+    : 'Check whether the jail `_jail` has the same FreeBSD userland version
+    as the host the the current process runs.
+
+    Args:
+        _jail: the running jail (name or jail id) to check for
+
+    Returns:
+        0 if the userland versions match, 1 otherwise
+
+    '
+    local _jail _host_version _jail_version
+
+    _jail="$1"
+
+    _host_version=$(/bin/freebsd-version -u) || exit 1
+    _jail_version=$(jexec -l -- "${_jail}" /bin/freebsd-version -u) || exit 1
+    if [ "${_host_version%%-*}" = "${_jail_version%%-*}" ]; then
+        return 0
+    fi
+    return 1
+}
+
+
+command_uversion() {
+    : ' Do a local `freebsd-version -u` and also for all running jails
+
+    '
+    echo "LOCALHOST: $(/bin/freebsd-version -u)"
+    for _jail in $(jls -N | awk '{if(NR>1)print $1}' | sort); do
+        echo "${_jail}: $(jexec -l -- "${_jail}" /bin/freebsd-version -u)"
+    done
+}
+
+
+command_audit() {
+    : 'Do a local `pkg audit -Fr` and also for all running jails
+
+    '
+    echo "${FPKG_SIGN}LOCALHOST"
+    pkg audit ${FPKG_AUDIT_FLAGS}
+    for _j in $(jls -N | awk '{if(NR>1)print $1}' | sort); do
+        echo ""
+        echo "${FPKG_SIGN}JAIL: ${_j}"
+        if has_same_userland_version "${_j}"; then
+            pkg -j "${_j}" audit ${FPKG_AUDIT_FLAGS}
+        else
+            echo "${FPKG_SKIPSIGN}SKIPPED because of different userland"
+        fi
+    done
+}
+
+
+command_update() {
+    : 'Do a local `pkg update` and also for all running jails
+
+    '
+    echo "${FPKG_SIGN}LOCALHOST"
+    pkg update ${FPKG_UPDATE_FLAGS}
+    for _j in $(jls -N | awk '{if(NR>1)print $1}' | sort); do
+        echo ""
+        echo "${FPKG_SIGN}JAIL: ${_j}"
+        if has_same_userland_version "${_j}"; then
+            pkg -j "${_j}" update ${FPKG_UPDATE_FLAGS}
+        else
+            echo "${FPKG_SKIPSIGN}SKIPPED because of different userland"
+        fi
+    done
+}
+
+
+command_upgrade() {
+    : 'Do a local `pkg upgrade` and also for all running jails
+
+    '
+    echo "${FPKG_SIGN}LOCALHOST"
+    pkg upgrade ${FPKG_UPGRADE_FLAGS}
+    for _j in $(jls -N | awk '{if(NR>1)print $1}' | sort); do
+        echo ""
+        echo "${FPKG_SIGN}JAIL: ${_j}"
+        if has_same_userland_version "${_j}"; then
+            pkg -j "${_j}" upgrade ${FPKG_UPGRADE_FLAGS}
+        else
+            echo "${FPKG_SKIPSIGN}SKIPPED because of different userland"
+        fi
+    done
+}
+
+
+command_check_upgrade() {
+    : 'Do a local `pkg upgrade -n` and also for all running jails
+
+    '
+    echo "${FPKG_SIGN}LOCALHOST"
+    pkg upgrade -n ${FPKG_UPGRADE_FLAGS}
+    for _j in $(jls -N | awk '{if(NR>1)print $1}' | sort); do
+        echo ""
+        echo "${FPKG_SIGN}JAIL: ${_j}"
+        if has_same_userland_version "${_j}"; then
+            pkg -j "${_j}" upgrade -n ${FPKG_UPGRADE_FLAGS}
+        else
+            echo "${FPKG_SKIPSIGN}SKIPPED because of different userland"
+        fi
+    done
+}
+
+
+command_check_fasttrack() {
+    : 'Check the fast-track repository versions against the canonical
+    FreeBSD repository versions.
+
+    Input (Globals):
+        FREEBSD_REPO:       the (canonical) FreeBSD repository name
+        LOCALBSDPORTS_REPO: the fast-track repository name
+
+    '
+    local _name local _repo _j
+
+    echo "${FPKG_SIGN}LOCALHOST"
+    pkg query '%n %R' |
+        while read _name _repo; do
+            if [ "${_repo}" = "${LOCALBSDPORTS_REPO}" ]; then
+                echo "   ${_name}"
+                printf "      %-15s : %s\n" "${LOCALBSDPORTS_REPO}" "$(pkg version -U -r ${LOCALBSDPORTS_REPO} -n ${_name} -v)"
+                printf "      %-15s : %s\n" "${FREEBSD_REPO}" "$(pkg version -U -r ${FREEBSD_REPO} -n ${_name} -v)"
+            fi
+        done
+    for _j in $(jls -N | awk '{if(NR>1)print $1}' | sort); do
+        echo ""
+        echo "${FPKG_SIGN}JAIL: ${_j}"
+        if has_same_userland_version "${_j}"; then
+            pkg -j "${_j}" query '%n %R' |
+                while read _name _repo; do
+                    if [ "${_repo}" = "${LOCALBSDPORTS_REPO}" ]; then
+                        echo "   ${_name}"
+                        printf "      %s-15s : %s\n" "${LOCALBSDPORTS_REPO}" "$(pkg -j ${_j} version -U -r ${LOCALBSDPORTS_REPO} -n ${_name} -v)"
+                        printf "      %-15s : %s\n" "${FREEBSD_REPO}" "$(pkg -j ${_j} version -U -r ${FREEBSD_REPO} -n ${_name} -v)"
+            fi
+                done
+        else
+            echo "${FPKG_SKIPSIGN}SKIPPED because of different userland"
+        fi
+    done
+}
+
+
+command_config() {
+    : 'The `pkg config name` command on the host and all running
+    compatible jails
+
+    Args:
+        _name: the configuration option to retrieve to
+
+    '
+    local _name
+
+    _name="$1"
+
+    if [ -z "${_name}" ]; then
+        echo "Usage: fpkg config <name>" >&2
+        return 1
+    fi
+    echo "${FPKG_SIGN}LOCALHOST"
+    pkg config "${_name}"
+    for _j in $(jls -N | awk '{if(NR>1)print $1}' | sort); do
+        echo ""
+        echo "${FPKG_SIGN}JAIL: ${_j}"
+        if has_same_userland_version "${_j}"; then
+            # This prints the value on the *host* also
+            #pkg -j "${_j}" config "${_name}"
+            jexec -- "${_j}" pkg config "${_name}"
+        else
+            echo "${FPKG_SKIPSIGN}SKIPPED because of different userland"
+        fi
+    done
+}
+
+
+command_vv() {
+    : 'The `pkg -vv` command on the host and all running compatible jails
+
+    '
+    echo "${FPKG_SIGN}LOCALHOST"
+    pkg -vv
+    for _j in $(jls -N | awk '{if(NR>1)print $1}' | sort); do
+        echo ""
+        echo "${FPKG_SIGN}JAIL: ${_j}"
+        if has_same_userland_version "${_j}"; then
+            pkg -j "${_j}" -vv
+        else
+            echo "${FPKG_SKIPSIGN}SKIPPED because of different userland"
+        fi
+    done
+}
+
+
+#
+# Global option handling
+#
+while getopts "Vh" _opt ; do
+    case ${_opt} in
+        V)
+            printf 'fpkg %s\n' '@@SIMPLEVERSIONSTR@@'
+            exit 0
+            ;;
+        h)
+            echo "${USAGE}"
+            exit 0
+            ;;
+        \?)
+            exit 2;
+            ;;
+        *)
+            echo "ERROR: option handling failed" >&2
+            exit 2
+            ;;
+    esac
+done
+
+#
+# Reset the Shell's option handling system to prepare for handling
+# command-local options.
+#
+shift $((OPTIND-1))
+OPTIND=1
+
+command="$1"
+shift
+
+test -n "$command" || { echo "ERROR: no command given" >&2; exit 2; }
+
+case "${command}" in
+    audit)
+        command_audit "$@"
+        ;;
+    update)
+        command_update "$@"
+        ;;
+    upgrade)
+        command_upgrade "$@"
+        ;;
+    check-upgrade|check_upgrade|upgrade-check|upgrade_check)
+        command_check_upgrade "$@"
+        ;;
+    check-fast-track|check-fasttrack|check_fast_track|check_fasttrack)
+        command_check_fasttrack "$@"
+        ;;
+    config)
+        command_config "$@"
+        ;;
+    uversion)
+        command_uversion "$@"
+        ;;
+    vv)
+        command_vv "$@"
+        ;;
+    *)
+        echo "ERROR: unknown command \`${command}'" >&2
+        exit 2;
+        ;;
+esac
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/ftjail	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,1156 @@
+#!/bin/sh
+# -*- indent-tabs-mode: nil; -*-
+#:
+#: A very minimal BSD Thin Jail management tool.
+#:
+#: :Author:    Franz Glasner
+#: :Copyright: (c) 2022-2024 Franz Glasner.
+#:             All rights reserved.
+#: :License:   BSD 3-Clause "New" or "Revised" License.
+#:             See LICENSE for details.
+#:             If you cannot find LICENSE see
+#:             <https://opensource.org/licenses/BSD-3-Clause>
+#: :ID:        @(#)@@SIMPLEVERSIONTAG@@
+#:
+
+set -eu
+
+VERSION="@@VERSION@@"
+
+USAGE='
+USAGE: ftjail [ OPTIONS ] COMMAND [ COMMAND OPTIONS ] [ ARG ... ]
+
+OPTIONS:
+
+  -V    Print the program name and version number to stdout and exit
+
+  -h    Print this help message to stdout and exit
+
+COMMANDS:
+
+  datasets-tmpl -L|-P PARENT-BASE PARENT-SKELETON NAME
+
+  mount-tmpl -L|-P [-n] [-u] BASE-RO SKELETON-RW MOUNTPOINT
+
+  umount-tmpl BASE-RO SKELETON-RW
+
+  interlink-tmpl MOUNTPOINT
+
+  populate-tmpl -L|-P DIRECTORY BASETXZ
+
+  snapshot-tmpl BASE-RO SKELETON-RW SNAPSHOT-NAME
+
+  copy-skel [-A] [-D] [-L] [-M MOUNTPOINT] [-P] [-u]  SOURCE-DS SNAPSHOT-NAME TARGET-DS
+
+  build-etcupdate-current-tmpl DIRECTORY TARBALL
+
+  freebsd-update [-k] [-o OLD-ORIGIN] DIRECTORY NEW-ORIGIN [ETCUPDATE-TARBALL]
+
+ENVIRONMENT:
+
+  All environment variables that affect "zfs" are effective also.
+
+DESCRIPTION:
+
+  All commands with the exception of "populate-tmpl" require ZFS as
+  filesystem.
+'
+
+
+_p_datadir="$(dirname "$0")"/../share/local-bsdtools
+. "${_p_datadir}/common.subr"
+
+
+# Reset to standard umask
+umask 0022
+
+
+#
+# PARENT-BASE NAME DRY-RUN
+#
+command_datasets_tmpl_base() {
+    local _p_base _name
+
+    local _opt_dry_run
+
+    local _ds_base _opt
+
+    _opt_dry_run=""
+    while getopts "nu" _opt ; do
+        case ${_opt} in
+            n|u)
+                _opt_dry_run="yes"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    _p_base="${1-}"
+    _name="${2-}"
+
+    if [ -z "${_p_base}" ]; then
+        echo "ERROR: no parent dataset for base given" >&2
+        return 2
+    fi
+    if [ -z "${_name}" ]; then
+        echo "ERROR: no name given" >&2
+        return 2
+    fi
+
+    if ! zfs list -H -o mountpoint -t filesystem "${_p_base}" >/dev/null 2>/dev/null; then
+        echo "ERROR: parent dataset \`${_p_base}' does not exist" >&2
+        return 1
+    fi
+    _ds_base="${_p_base}/${_name}"
+    if zfs list -H -o mountpoint -t filesystem "${_ds_base}" >/dev/null 2>/dev/null; then
+        echo "ERROR: dataset \`${_ds_base}' does already exist" >&2
+        return 1
+    fi
+
+
+    [ "${_opt_dry_run}" = "yes" ] && return 0
+
+    echo "Creating RO base datasets in:"
+    printf "\\t%s\\n" "${_ds_base}"
+
+    zfs create -u -o canmount=noauto "${_ds_base}"
+
+}
+
+
+#
+# SKELETON NAME DRY-RUN
+#
+command_datasets_tmpl_skel() {
+    local _p_base _name
+
+    local _opt_dry_run _opt_symlink
+
+    local _ds_skel _child _child_zfsopts _opt
+
+    _opt_dry_run=""
+    _opt_symlink=""
+
+    while getopts "LPnu" _opt ; do
+        case ${_opt} in
+            L)
+                _opt_symlink="yes"
+                ;;
+            P)
+                _opt_symlink="no"
+                ;;
+            n|u)
+                _opt_dry_run="yes"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    [ -z "${_opt_symlink}" ] && { echo "ERROR: -L or -P must be given" 1>&2; return 2; }
+
+    _p_skel="${1-}"
+    _name="${2-}"
+
+    if [ -z "${_p_skel}" ]; then
+        echo "ERROR: no parent dataset for skeleton given" >&2
+        return 2
+    fi
+    if [ -z "${_name}" ]; then
+        echo "ERROR: no name given" >&2
+        return 2
+    fi
+
+    if ! zfs list -H -o mountpoint -t filesystem "${_p_skel}" >/dev/null 2>/dev/null; then
+        echo "ERROR: parent dataset \`${_p_skel}' does not exist" >&2
+        return 1
+    fi
+    _ds_skel="${_p_skel}/${_name}"
+    if zfs list -H -o mountpoint -t filesystem "${_ds_skel}" >/dev/null 2>/dev/null; then
+        echo "ERROR: dataset \`${_ds_skel}' does already exist" >&2
+        return 1
+    fi
+
+    [ "${_opt_dry_run}" = "yes" ] && return 0
+
+    echo "Creating RW skeleton datasets in:"
+    printf "\\t%s\\n" "${_ds_skel}"
+
+    if [ "${_opt_symlink}" = "yes" ]; then
+        # In this case the skeleton root needs to be mounted into a "skeleton" subdir
+        zfs create -u -o canmount=noauto "${_ds_skel}"
+    else
+        # Only children are to be mounted
+        zfs create -u -o canmount=off "${_ds_skel}"
+    fi
+    # "usr" is only a container holding "usr/local"
+    zfs create -u -o canmount=off "${_ds_skel}/usr"
+    #
+    # XXX FIXME: What about usr/ports/distfiles
+    #            We typically want to use binary packages.
+    #            And if we use ports they are not in usr/ports typically.
+    #
+    #zfs create -u -o canmount=off "${_ds_skel}/usr/ports"
+    #
+    # XXX FIXME: What about home
+    #
+    # /var/mail is here because it relies on atime
+    #
+    for _child in etc home root tmp usr/local var var/mail ; do
+        case "${_child}" in
+            "tmp"|"var/tmp")
+                _child_zfsopts="-o sync=disabled -o setuid=off"
+                ;;
+            "home")
+                _child_zfsopts="-o setuid=off"
+                ;;
+            "usr/ports/distfiles")
+                _child_zfsopts="-o exec=off -o setuid=off -o compression=off -o primarycache=metadata"
+                ;;
+            "var/mail")
+                _child_zfsopts="-o atime=on -o exec=off -o setuid=off"
+                ;;
+            *)
+                _child_zfsopts=""
+                ;;
+        esac
+        zfs create -u -o canmount=noauto ${_child_zfsopts} "${_ds_skel}/${_child}"
+    done
+}
+
+
+#
+# "datasets-tmpl" -- create the ZFS dataset tree
+#
+# PARENT-BASE PARENT-SKELETON NAME
+#
+command_datasets_tmpl() {
+    # parent ZFS dataset -- child ZFS dataset name
+    local _p_base _p_skel _name
+    local _opt_symlink
+
+    local _ds_base _ds_skel _opt
+
+    _opt_symlink=""
+
+    while getopts "LP" _opt ; do
+        case ${_opt} in
+            L)
+                _opt_symlink="-L"
+                ;;
+            P)
+                _opt_symlink="-P"
+                ;;
+            \?)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    [ -z "${_opt_symlink}" ] && { echo "ERROR: -L or -P must be given" 1>&2; return 2; }
+
+    _p_base="${1-}"
+    _p_skel="${2-}"
+    _name="${3-}"
+
+    # Check preconditions
+    command_datasets_tmpl_base -n "${_p_base}" "${_name}" || return
+    command_datasets_tmpl_skel -n ${_opt_symlink} "${_p_skel}" "${_name}" || return
+
+    # Really do it
+    command_datasets_tmpl_base "${_p_base}" "${_name}" || return
+    command_datasets_tmpl_skel ${_opt_symlink} "${_p_skel}" "${_name}" || return
+    return 0
+}
+
+
+#
+# "populate-tmpl" -- populate the datasets with content from a FreeBSD base.txz
+#
+# command_populate_tmpl mountpoint basetxz
+#
+command_populate_tmpl() {
+    # MOUNTPOINT -- base.txz
+    local _mp _basetxz
+    local _opt_symlink _opt_preserve_boot
+
+    local _opt _dir
+
+    _opt_symlink=""
+    _opt_preserve_boot=""
+
+    while getopts "LPb" _opt ; do
+        case ${_opt} in
+            L)
+                _opt_symlink="yes"
+                ;;
+            P)
+                _opt_symlink="no"
+                ;;
+            b)
+                _opt_preserve_boot="yes"
+                ;;
+            \?)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    [ -z "${_opt_symlink}" ] && { echo "ERROR: -L or -P must be given" 1>&2; return 2; }
+
+    _mp="${1-}"
+    _basetxz="${2-}"
+    _kerneltxz="${3-}"
+
+    if [ -z "${_mp}" ]; then
+        echo "ERROR: no mountpoint given" >&2
+        return 2
+    fi
+    if [ -z "${_basetxz}" ]; then
+        echo "ERROR: no base.txz given" >&2
+        return 2
+    fi
+    if [ ! -d "${_mp}" ]; then
+        echo "ERROR: mountpoint \`${_mp}' does not exist" >&2
+        return 1
+    fi
+    if [ ! -r "${_basetxz}" ]; then
+        echo "ERROR: file \`${_basetxz}' is not readable" >&2
+        return 1
+    fi
+
+    if [ "${_opt_symlink}" = "yes" ]; then
+        echo "Extracting RO base ..."
+        tar -C "${_mp}" --exclude=./etc --exclude=./root --exclude=./tmp --exclude=./usr/local --exclude=./var --no-safe-writes -xJp -f "${_basetxz}" || return
+        # "home" is not part of base
+        for _dir in etc root tmp usr/local var ; do
+            echo "Extracting RW skeleton: ${_dir} ..."
+            tar -C "${_mp}/skeleton" --include="./${_dir}" --exclude=./root/.cshrc --exclude=./root/.profile -xJp -f "${_basetxz}" || return
+        done
+        # In the original archive they are archived as hardlinks: make proper symlinks here
+        (cd "${_mp}/skeleton/root" && ln -s ../../.profile .profile) || return
+        (cd "${_mp}/skeleton/root" && ln -s ../../.cshrc .cshrc) || return
+    else
+        echo "Extracting base ..."
+        tar -C "${_mp}" --exclude=./root/.cshrc --exclude=./root/.profile --no-safe-writes -xJp -f "${_basetxz}" || return
+        # In the original archive they are archived as hardlinks: make proper symlinks here
+        (cd "${_mp}/root" && ln -s ../.profile .profile) || return
+        (cd "${_mp}/root" && ln -s ../.cshrc .cshrc) || return
+    fi
+
+    if [ \( "${_opt_preserve_boot}" = "yes" \) -o \( -n "${_kerneltxz}" \) ]; then
+        if [ -n "${_kerneltxz}" ]; then
+            echo "Extracting kernel ..."
+            tar -C "${_mp}" -xJp -f "${_kerneltxz}" || return
+        else
+            echo "Preserved \"boot\""
+        fi
+    else
+        find "${_mp}/boot" -type f -delete || true
+    fi
+}
+
+
+#
+# _do_mount dataset mountpoint dry-run mount-natural childs-only
+#
+_do_mount() {
+    local _dsname _mountpoint _dry_run _mount_natural _childs_only
+
+    local _name _mp _canmount _mounted
+    local _rootds_mountpoint _relative_mp _real_mp
+
+    _dsname="${1}"
+    _mountpoint="${2}"
+    _dry_run="${3}"
+    _mount_natural="${4}"
+    _childs_only="${5}"
+
+    if [ -z "${_dsname}" ]; then
+        echo "ERROR: no dataset given" >&2
+        return 2
+    fi
+
+    _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")"  || \
+        { echo "ERROR: root dataset \`${_dsname}' does not exist" >&2; return 1; }
+
+    if [ -z "${_mountpoint}" ]; then
+        if [ "${_mount_natural}" = "yes" ]; then
+            _mountpoint="${_rootds_mountpoint}"
+        else
+            echo "ERROR: no mountpoint given" >&2
+            return 2
+        fi
+    else
+        if [ "${_mount_natural}" = "yes" ]; then
+            echo "ERROR: Cannot have a custom mountpoint when mount-natural is activated" >&2
+            return 2
+        fi
+    fi
+
+    # Eventually remove a trailing slash
+    _mountpoint="${_mountpoint%/}"
+    if [ -z "${_mountpoint}" ]; then
+        echo "ERROR: would mount over the root filesystem" >&2
+        return 1
+    fi
+
+    zfs list -H -o name,mountpoint,canmount,mounted -s mountpoint -t filesystem -r "${_dsname}" \
+    | {
+        while IFS=$'\t' read -r _name _mp _canmount _mounted ; do
+            # Skip filesystems that are already mounted
+            [ "${_mounted}" = "yes" ] && continue
+            # Skip filesystems that must not be mounted
+            [ "${_canmount}" = "off" ] && continue
+            case "${_mp}" in
+                "none"|"legacy")
+                    # Do nothing for filesystem with unset or legacy mountpoints
+                    ;;
+                "${_rootds_mountpoint}"|"${_rootds_mountpoint}/"*)
+                    #
+                    # Handle only mountpoints that have a mountpoint below
+                    # the parent datasets mountpoint
+                    #
+
+                    # Determine the mountpoint relative to the parent mountpoint
+                    _relative_mp="${_mp#${_rootds_mountpoint}}"
+                    # Eventually remove a trailing slash
+                    _relative_mp="${_relative_mp%/}"
+                    # The real effective full mountpoint
+                    _real_mp="${_mountpoint}${_relative_mp}"
+
+                    #
+                    # Consistency and sanity check: computed real mountpoint must
+                    # be equal to the configured mountpoint when no custom mountpoint
+                    # is given.
+                    #
+                    if [ "${_mount_natural}" = "yes" ]; then
+                        if [ "${_real_mp}" != "${_mp}" ]; then
+                            echo "ERROR: mountpoint mismatch" 1>&2
+                            return 1
+                        fi
+                    fi
+
+                    if [ \( "${_childs_only}" = "yes" \) -a \( "${_name}" = "${_dsname}" \) ]; then
+                        echo "Skipping ${_name} because mounting childs only" 1>&2
+                    else
+                        if [ "${_dry_run}" = "yes" ]; then
+                            echo "Would mount ${_name} on ${_real_mp}"
+                        else
+                            mkdir -p "${_real_mp}" 1> /dev/null 2> /dev/null || \
+                                { echo "ERROR: cannot create mountpoint ${_real_mp}" 1>&2; return 1; }
+                            echo "Mounting ${_name} on ${_real_mp}"
+                            mount -t zfs "${_name}" "${_real_mp}" || return 1
+                        fi
+                    fi
+                    ;;
+                *)
+                    echo "Skipping ${_name} because its configured ZFS mountpoint is not relative to given root dataset" 1>&2
+                    ;;
+            esac
+        done
+
+        return 0
+    }
+}
+
+
+#
+# "mount-tmpl" -- recursively mount a base and skeleton datasets including subordinate datasets
+#
+# command_mount_tmpl base-ro skeleton-rw  mountpoint
+#
+command_mount_tmpl() {
+    local _ds_base _ds_skel _mountpoint
+    local _opt_dry_run _opt_symlink
+
+    local _opt
+
+    _opt_dry_run=""
+    _opt_symlink=""
+
+    while getopts "LPnu" _opt ; do
+        case ${_opt} in
+            L)
+                _opt_symlink="yes"
+                ;;
+            P)
+                _opt_symlink="no"
+                ;;
+            n|u)
+                _opt_dry_run="yes"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    [ -z "${_opt_symlink}" ] && { echo "ERROR: -L or -P must be given" 1>&2; return 2; }
+
+    _ds_base="${1-}"
+    _ds_skel="${2-}"
+    _mountpoint="${3-}"
+
+    _do_mount "${_ds_base}" "${_mountpoint}" "${_opt_dry_run}" "" "" || return
+    if [ "${_opt_symlink}" = "yes" ]; then
+        if [ "${_opt_dry_run}" != "yes" ]; then
+            if [ ! -d "${_mountpoint}/skeleton" ]; then
+                mkdir "${_mountpoint}/skeleton" || return
+            fi
+        fi
+        _do_mount "${_ds_skel}" "${_mountpoint}/skeleton" "${_opt_dry_run}" "" "" || return
+    else
+        _do_mount "${_ds_skel}" "${_mountpoint}" "${_opt_dry_run}" "" "yes" || return
+    fi
+
+    return 0
+}
+
+
+#
+# _do_umount dataset
+#
+_do_umount() {
+    local _dsname
+
+    local _name _mp _rest
+    local _rootds_mountpoint
+
+    _dsname="${1}"
+    [ -z "${_dsname}" ] && { echo "ERROR: no dataset given" >&2; return 2; }
+
+    # Just determine whether the given dataset name exists
+    _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")" || \
+        { echo "ERROR: dataset not found" >&2; return 1; }
+
+    mount -t zfs -p \
+    | grep -E "^${_dsname}(/|\s)" \
+    | sort -n -r \
+    | {
+        while IFS=' '$'\t' read -r _name _mp _rest ; do
+            echo "Umounting ${_name} on ${_mp}"
+            umount "${_mp}" || return 1
+        done
+        return 0
+    }
+}
+
+
+#
+# "umount-tmpl" -- umount skeleton and base datasets
+#
+# command_umount_tmpl ds-base ds-skeleton
+#
+command_umount_tmpl() {
+    local _ds_base _ds_skel
+
+    _ensure_no_options "$@"
+
+    _ds_base="${1-}"
+    _ds_skel="${2-}"
+
+    [ -z "${_ds_base}" ] && { echo "ERROR: no RO base dataset given" >&2; return 2; }
+    [ -z "${_ds_skel}" ] && { echo "ERROR: no RW skeleton dataset given" >&2; return 2; }
+
+    _do_umount "${_ds_skel}" || return
+    _do_umount "${_ds_base}" || return
+
+    return 0
+}
+
+
+#
+# "interlink-tmpl" -- create links from base to skeleton
+#
+# command_interlink_tmpl mountpint
+#
+command_interlink_tmpl() {
+    local _mountpoint
+
+    local _dir _dirpart _basepart
+
+    _ensure_no_options "$@"
+
+    _mountpoint="${1-}"
+
+    [ -z "${_mountpoint}" ] && { echo "ERROR: no mountpoint given" 1>&2; return 2; }
+    [ -d "${_mountpoint}" ] || { echo "ERROR: mountpoint \`${_mountpoint}' does not exist" 1>&2; return 1; }
+    [ -d "${_mountpoint}/skeleton" ] || { echo "WARNING: skeleton is not mounted at \`${_mountpoint}/skeleton'" 1>&2; }
+
+    for _dir in etc home root tmp usr/local var ; do
+        case "${_dir}" in
+            "usr/local")
+                _dirpart="$(dirname "${_dir}")"
+                _basepart="$(basename "${_dir}")"
+                [ -d "${_mountpoint}/${_dirpart}" ] || mkdir "${_mountpoint}/${_dirpart}" || return
+                ( cd "${_mountpoint}/${_dirpart}" && ln -s "../skeleton/${_dir}" "${_basepart}" ) || return
+                ;;
+            *)
+                ( cd "${_mountpoint}" && ln -s "skeleton/${_dir}" "${_dir}" ) || return
+                ;;
+        esac
+    done
+    return 0
+}
+
+#:
+#: Create a snapshot for a dataset and all of its children.
+#:
+#: Args:
+#:     $1: the datasets
+#:     $2: the name of the snapshot
+#:
+_do_snapshot() {
+    local _ds _snap_name
+
+    _ds="${1}"
+    _snap_name="${2}"
+
+    [ -z "${_ds}" ] && { echo "ERROR: no dataset given" 1>&2; return 2; }
+    [ -z "${_snap_name}" ] && { echo "ERROR: no snapshot name given" 1>&2; return 2; }
+
+    if ! zfs list -H -o name -t filesystem "${_ds}" 1> /dev/null 2> /dev/null ; then
+        echo "ERROR: parent dataset \`${_ds}' does not exist or is not available" 1>&2
+        return 1
+    fi
+
+    zfs snapshot -r "${_ds}@${_snap_name}" || return
+}
+
+
+#:
+#: Implement the "snapshot-tmpl" command
+#:
+command_snapshot_tmpl() {
+    local _ds_base _ds_skel _snap_name
+
+    _ensure_no_options "$@"
+
+    _ds_base="${1-}"
+    _ds_skel="${2-}"
+    _snap_name="${3-}"
+
+    # Here extra checks because of better error messages possible
+    [ -z "${_ds_base}" ] && { echo "ERROR: no RO base dataset name given" 1>&2; return 2; }
+    [ -z "${_ds_skel}" ] && { echo "ERROR: no RW skeleton dataset name given" 1>&2; return 2; }
+
+    _do_snapshot "${_ds_base}" "${_snap_name}" || return
+    _do_snapshot "${_ds_skel}" "${_snap_name}" || return
+    return 0
+}
+
+
+#:
+#: Implementation of "copy-skel"
+#:
+command_copy_skel() {
+    local _ds_source _snapshot_name _ds_target
+    local _opt_symlink _opt_nomount _opt_canmount _opt_mountpoint _opt_nodata
+
+    local _opt _name _relative_name  _root_canmount
+
+    _opt_symlink=""
+    _opt_nomount=""
+    _opt_canmount="-o canmount=on"
+    _opt_mountpoint=""
+    _opt_nodata=""
+
+    while getopts "ADLM:Pu" _opt ; do
+        case ${_opt} in
+            A)
+                _opt_canmount="-o canmount=noauto"
+                ;;
+            D)
+                _opt_nodata="yes"
+                ;;
+            L)
+                _opt_symlink="yes"
+                ;;
+            M)
+                _opt_mountpoint="${OPTARG}"
+                ;;
+            P)
+                _opt_symlink="no"
+                ;;
+            u)
+                _opt_nomount="-u"
+                ;;
+            \?)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    [ -z "${_opt_symlink}" ] && { echo "ERROR: -L or -P must be given" 1>&2; return 2; }
+    [ \( "${_opt_nodata}" = "yes" \) -a \( "${_opt_symlink}" = "yes" \) ] && { echo "ERROR: -L and -D are incompatible" 1>&2; return 2; }
+
+    _ds_source="${1-}"
+    _snapshot_name="${2-}"
+    _ds_target="${3-}"
+
+    [ -z "${_ds_source}" ] && { echo "ERROR: no source given" 1>&2; return 2; }
+    [ -z "${_snapshot_name}" ] && { echo "ERROR: no snapshot name given" 1>&2; return 2; }
+    [ -z "${_ds_target}" ] && { echo "ERROR: no target given" 1>&2; return 2; }
+
+    zfs list -r -t all -o name "${_ds_source}" \
+    | {
+        while IFS=$'\t' read -r _name ; do
+            if [ "${_name}" = "${_name%@*}@${_snapshot_name}" ]; then
+                echo "FOUND: $_name"
+                # Determine the relative name of the dataset
+                _relative_name="${_name#${_ds_source}}"
+                _relative_name="${_relative_name%@*}"
+                echo "  -> $_relative_name"
+                if [ -z "${_relative_name}" ]; then
+                    #
+                    # Root
+                    #
+                    if [ "${_opt_symlink}" = "yes" ]; then
+                        _root_canmount="${_opt_canmount}"
+                    else
+                        _root_canmount="-o canmount=off"
+                    fi
+                    if [ -n "${_opt_mountpoint}" ]; then
+                        zfs send -Lec -p -v "${_name}" | zfs receive ${_opt_nomount} -v ${_root_canmount} -o "mountpoint=${_opt_mountpoint}" -o readonly=off "${_ds_target}${_relative_name}"
+                    else
+                        zfs send -Lec -p -v "${_name}" | zfs receive ${_opt_nomount} -o readonly=off -v ${_root_canmount} -x mountpoint "${_ds_target}${_relative_name}"
+                    fi
+                else
+                    #
+                    # Children
+                    #
+                    if [ "${_relative_name}" = "/usr" ]; then
+                        zfs send -Lec -p -v "${_name}" | zfs receive ${_opt_nomount} -v -o canmount=off -x mountpoint "${_ds_target}${_relative_name}"
+                    else
+                        zfs send -Lec -p -v "${_name}" | zfs receive ${_opt_nomount} -v ${_opt_canmount} -x mountpoint "${_ds_target}${_relative_name}"
+                    fi
+                fi
+            fi
+        done
+    }
+    # Need only the filesystem data (no associated snapshots)
+    echo "Destroying unneeded snapshots ..."
+    zfs destroy -rv "${_ds_target}@${_snapshot_name}"
+}
+
+
+#:
+#: Implement the "build-etcupdate-current-tmpl" command
+#:
+command_build_etcupdate_current_tmpl() {
+    local _directory _tarball
+
+    _directory="${1-}"
+    _tarball="${2-}"
+
+    [ -z "${_directory}" ] && { echo "ERROR: no directory given" 1>&2; return 2; }
+    [ -z "${_tarball}" ] && { echo "ERROR: no directory given" 1>&2; return 2; }
+    [ -e "${_tarball}" ] && { echo "ERROR: \`${_tarball}' exists already" 1>&2; return 1; }
+
+    if ! tar -cjf "${_tarball}" -C "${_directory}/var/db/etcupdate/current" . ; then
+        rm -f "${_tarball}" || true
+        return 1
+    fi
+}
+
+
+#:
+#: Determine extra clone options with respect to the "mountpoint" property
+#:
+#: Args:
+#:   $1: the dataset
+#:
+#: Output (stdout)
+#:   The extra clone arguments
+#:
+#: Exit:
+#:   On unexpected source values
+#:
+_get_clone_extra_prop_for_mountpoint() {
+    local ds
+
+    local _mp_name _mp_property _mp_value _mp_source
+
+    ds="${1}"
+
+    zfs get -H mountpoint "${ds}" \
+    | {
+        IFS=$'\t' read -r _mp_name _mp_property _mp_value _mp_source
+        case "${_mp_source}" in
+            local)
+                echo -n "-o mountpoint=${_mp_value}"
+                ;;
+            default|inherited*)
+                ;;
+            temporary*|received*|'-'|none)
+                # XXX FIXME: Is this relevant on FreeBSD?
+                echo "ERROR: Unexpected SOURCE \"${_mp_source}\" for mountpoint at \`${_mp_value}'" 1>&2
+                exit 1
+                ;;
+            *)
+                echo "ERROR: Unexpected SOURCE for mountpoint property at \`${_mp_value}'" 1>&2
+                exit 1;
+                ;;
+        esac
+        if [ "${_mp_value}" != "${_directory}" ]; then
+            echo "WARNING: dataset is not mounted at its configured mountpoint but elsewhere (probably via \"mount -t zfs\")" 1>&2
+        fi
+    }
+}
+
+
+#:
+#: Determine the "canmount" property for a dataset
+#:
+#: Args:
+#:   $1: the dataset
+#:
+#: Output (stdout):
+#:   The local value or "DEFAULT" for the (unset) default
+#:
+#: Exit:
+#:   On unexpected source values
+#:
+_get_canmount_setting_for_dataset() {
+    local ds
+
+    local _cm_name _cm_property _cm_value _cm_source
+
+    ds="${1}"
+
+    zfs get -H canmount "${ds}" \
+    | {
+        IFS=$'\t' read -r _cm_name _cm_property _cm_value _cm_source
+        case "${_cm_source}" in
+            local)
+                echo -n "canmount=${_cm_value}"
+                ;;
+            default)
+                echo -n "DEFAULT"
+                ;;
+            inherited|temporary*|received*|'-'|none)
+                # XXX FIXME: Is this relevant on FreeBSD?
+                echo "ERROR: Unexpected SOURCE \"${_cm_source}\" for canmount at \`${_cm_name}'" 1>&2
+                exit 1
+                ;;
+            *)
+                echo "ERROR: Unexpected SOURCE for canmount property at \`${_cm_name}'" 1>&2
+                exit 1;
+                ;;
+        esac
+    }
+}
+
+
+#:
+#: Implement the "freebsd-update" command for a thin jail
+#:
+#: .. note:: FreeBSD's :command:`etcupdate` also executes
+#:           :command:`certctl rehash` if certs are to be added or removed!
+#:
+command_freebsd_update() {
+    local _directory _new_origin _etcupdate_tarball
+    local _opt_keep _opt_old_origin
+
+    local _res _jailname _dir_mounts _dir_fn_fstab _dir_basename _dir_fn_tldir
+    local _root_dataset _root_mountpoint _root_type _root_options
+    local _clone_extra_props _canmount_prop
+    local _line _opt
+    local _root_readonly _root_origin
+    local _u_tmpdir
+    local _add_log_sock
+
+    _opt_keep="no"
+    _opt_old_origin=""
+    while getopts "ko:" _opt ; do
+        case ${_opt} in
+            k)
+                _opt_keep="yes"
+                ;;
+            o)
+                _opt_old_origin="$OPTARG"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    _directory="${1-}"
+    _new_origin="${2-}"
+    _etcupdate_tarball="${3-}"
+
+    [ -z "${_directory}" ] && { echo "ERROR: no directory given" 1>&2; return 2; }
+    [ -d "${_directory}" ] || { echo "ERROR: directory \`${_directory}' does not exist" 1>&2; return 1; }
+
+    [ -z "${_new_origin}" ] && { echo "ERROR: no new origin given" 1>&2; return 2; }
+    zfs list -H -o name -t snapshot "${_new_origin}" >/dev/null || { echo "ERROR: new origin does not exist" 1>&2; return 1; }
+    if [ -n "${_etcupdate_tarball}" ]; then
+        [ -f "${_etcupdate_tarball}" ] || { echo "ERROR: given etcupdate tarball does not exist " 1>&2; return 1; }
+    fi
+
+    _dir_basename="$(basename ${_directory})"
+
+    set +e
+    _jailname=$(_get_jail_from_path "${_directory}")
+    _res=$?
+    set -e
+    if [ ${_res} -ne 3 ] ; then
+        if [ ${_res} -ne 0 ] ; then
+            return ${_res}
+        else
+            echo "ERROR: Please stop the \`${_jailname}' jail" >&2
+            return 1
+        fi
+    fi
+    #
+    # Check whether additional log sockets are opened at their default
+    # locations. Because they hinder proper unmounting of filesystems.
+    #
+    for _add_log_sock in /var/run/log /var/run/logpriv ; do
+        if [ -S "${_directory}${_add_log_sock}" ]; then
+            echo "ERROR: additional log socket is open at \`${_directory}${_add_log_sock}'" >&2
+            return 1
+        fi
+    done
+    #
+    # Check whether there are any open files within the jail.
+    #
+    # "procstat file" also lists fifo, socket, message queue, kgueue et al.
+    # file types.
+    #
+    # Note that procstat places extra whitespace at the end of lines sometimes.
+    #
+    #
+    if procstat -a file | egrep '['$'\t '']+'"${_directory}"'(/|(['$'\t '']*)$)' ; then
+        echo "ERROR: There are open files within the jail" >&2
+        return 1
+    fi
+    # The same for memory mappings
+    if procstat -a vm | egrep '['$'\t '']+'"${_directory}"'(/|(['$'\t '']*)$)' ; then
+        echo "ERROR: There are open memory mappings within the jail" >&2
+        return 1
+    fi
+
+    _dir_mounts="$(_get_mounts_at_directory "${_directory}")"
+
+    #
+    # Check preconditions thoroughly!
+    #
+    # Check that the first item/line is a read-only ZFS mount directly
+    # at the given directory. This must also be its configured
+    # mountpoint in ZFS.
+    # Also check that it is a clone proper.
+    #
+    IFS=' '$'\t' read -r _root_dataset _root_mountpoint _root_type _root_options _line <<EOF4tHGCSS
+${_dir_mounts}
+EOF4tHGCSS
+    [ "${_root_mountpoint}" != "${_directory}" ] && { echo "ERROR: found root mountpoint does not match given directory" 1>&2; return 1; }
+    [ "${_root_type}" != "zfs" ] && { echo "ERROR: root mountpoint is not from a ZFS dataset" 1>&2; return 1; }
+    _root_readonly="$(zfs list -H -o readonly "${_root_dataset}")"
+    [ "${_root_readonly}" != "on" ] &&  { echo "ERROR: the root dataset is not mounted read-only" 1>&2; return 1; }
+    _root_origin="$(zfs list -H -o origin "${_root_dataset}")"
+    if [ -n "${_opt_old_origin}" ]; then
+        [ "${_opt_old_origin}" != "${_root_origin}" ] && { echo "ERROR: origin mismatch" 1>&2; return 1; }
+    else
+        [ "${_root_origin}" = '-' ] &&  { echo "ERROR: the root dataset is not a ZFS clone" 1>&2; return 1; }
+    fi
+
+    # Determine we need to clone with a custom (non inherited) "mountpoint"
+    _clone_extra_props="$(_get_clone_extra_prop_for_mountpoint "${_root_dataset}") "
+    # Determine we need to clone with a custom (non inherited) "canmount"
+    _canmount_prop="$(_get_canmount_setting_for_dataset "${_root_dataset}")"
+
+    #
+    # XXX FIXME: should we check that _root_options equals "ro" or
+    #            start with "ro,"
+    #    _root_origin="$(zfs list -H -o origin "${_root_dataset}")"
+
+    _u_tmpdir="$(env TMPDIR=/var/tmp mktemp -d -t ftjail_${_dir_basename})"
+    [ -z "${_u_tmpdir}" ] && { echo "ERROR: cannot create unique temp dir" 1>&2; return 1; }
+    _dir_fn_fstab="${_u_tmpdir}/fstab"
+    echo -n "${_dir_mounts}" >>"${_dir_fn_fstab}"
+    _dir_fn_tldir="${_u_tmpdir}/tldirs"
+    find "${_directory}" -depth 1 -type d 2>/dev/null | sort >>"${_dir_fn_tldir}"
+
+    # Unmount in reverse order: unmount can do it for us
+    echo "Unmounting all datasets mounted at \`${_directory}'"
+    umount -a -F "${_dir_fn_fstab}" -v
+
+    #
+    # XXX TBD: Hooks to create some new top-level dirs (/srv /proc et
+    #          al.)  if needed: clone RW, mount, make the dirs,
+    #          umount, make the clone RO and continue "normally" by
+    #          completely mounting the stored fstab.
+    #
+
+    #
+    # Destroy the current read-only root clone and make a new clone based
+    # on the given new origin.
+    # The new clone temporarily is RW and is not to be mounted automatically.
+    # These both properties are set again below after the new base is
+    # adjusted properly.
+    #
+    echo "Destroying the cloned root dataset \`${_root_dataset}'"
+    zfs destroy -v "${_root_dataset}"
+    echo "Cloning a new root dataset \`${_root_dataset}' from new origin \`${_new_origin}'"
+    zfs clone -o readonly=off -o canmount=noauto ${_clone_extra_props} "${_new_origin}" "${_root_dataset}"
+    #
+    # NOTE: Always mount with "mount -t zfs" because a custom
+    #       mountpoint is not reflected in the "mountpoint"
+    #       property. So in scripts to be sure to unmount and re-mount
+    #       at the same location always use "mount -t zfs".
+    #
+    echo "Remounting only the root dataset at \`${_directory}'"
+    [ ! -d "${_directory}" ] && mkdir "${_directory}"
+    mount -t zfs "${_root_dataset}" "${_directory}"
+    #
+    # Re-create all currently missing top-level dirs (aka mountpoint)
+    # in the new clone. Most probably they serve as mountpoints for other
+    # datasets.
+    #
+    # XXX FIXME: Re-create the current mode bits and/or ACLs also.
+    #            But most probably they are set properly in the mounted
+    #            datasets.
+    #
+    echo "Recreating missing top-level directories"
+    cat "${_dir_fn_tldir}" \
+    | {
+        while IFS='' read -r _line ; do
+            if [ ! -d "${_line}" ]; then
+                echo "Recreating top-level directory: ${_line}"
+                mkdir "${_line}"
+            fi
+        done
+    }
+    echo "Unmounting the new root dataset"
+    umount "${_directory}"
+    echo "Re-setting some ZFS properties on the new cloned dataset"
+    zfs set readonly=on "${_root_dataset}"
+    #
+    # Copy "canmount" properly last because it has been set to "noauto"
+    # temporarily.
+    #
+    if [ -n "${_canmount_prop}" ]; then
+        if [ "${_canmount_prop}" = "DEFAULT" ]; then
+            #
+            # "zfs inherit" is not possible for "canmount".
+            # Use "inherit -S" to simulate a reset to "default" somewhat
+            #
+            # See also: https://github.com/openzfs/zfs/issues/5733
+            #
+            zfs inherit -S canmount "${_root_dataset}"
+        else
+            zfs set "${_canmount_prop}" "${_root_dataset}"
+        fi
+    fi
+
+    # Mount again
+    echo "Mounting all datasets rooted at \`${_directory}'"
+    [ ! -d "${_directory}" ] && mkdir "${_directory}"
+    mount -a -F "${_dir_fn_fstab}" -v
+
+    # Update and/or merge configs
+    if [ -n "${_etcupdate_tarball}" ]; then
+        echo "Calling etcupdate for DESTDIR=${_directory}"
+        etcupdate -D "${_directory}" -t "${_etcupdate_tarball}"
+    fi
+
+    if [ "${_opt_keep}" != "yes" ]; then
+        echo "Cleaning up..."""
+        [ -n "${_u_tmpdir}" ] && [ -d "${_u_tmpdir}" ] && rm -rvf "${_u_tmpdir}"
+    fi
+    echo "Done."
+}
+
+
+#
+# Global option handling
+#
+while getopts "Vh" _opt ; do
+    case ${_opt} in
+        V)
+            printf 'ftjail %s\n' '@@SIMPLEVERSIONSTR@@'
+            exit 0
+            ;;
+        h)
+            echo "${USAGE}"
+            exit 0
+            ;;
+        \?)
+            exit 2;
+            ;;
+        *)
+            echo "ERROR: option handling failed" 1>&2
+            exit 2
+            ;;
+    esac
+done
+
+#
+# Reset the Shell's option handling system to prepare for handling
+# command-local options.
+#
+shift $((OPTIND-1))
+OPTIND=1
+
+test $# -gt 0 || { echo "ERROR: no command given" 1>&2; exit 2; }
+
+command="$1"
+shift
+
+case "${command}" in
+    datasets-tmpl)
+        command_datasets_tmpl "$@"
+        ;;
+    mount-tmpl)
+        command_mount_tmpl "$@"
+        ;;
+    umount-tmpl|unmount-tmpl)
+        command_umount_tmpl "$@"
+        ;;
+    interlink-tmpl)
+        command_interlink_tmpl "$@"
+        ;;
+    populate-tmpl)
+        command_populate_tmpl "$@"
+        ;;
+    snapshot-tmpl)
+        command_snapshot_tmpl "$@"
+        ;;
+    copy-skel)
+        command_copy_skel "$@"
+        ;;
+    build-etcupdate-current-tmpl)
+        command_build_etcupdate_current_tmpl "$@"
+        ;;
+    configure)
+        echo "ERROR: use \`fjail configure' instead" 1>&2;
+        exit 2
+        ;;
+    freebsd-update)
+        command_freebsd_update "$@"
+        ;;
+    *)
+        echo "ERROR: unknown command \`${command}'" 1>&2
+        exit 2
+        ;;
+esac
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/fzfs	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,530 @@
+#!/bin/sh
+# -*- indent-tabs-mode: nil; -*-
+#:
+#: A ZFS management helper tool.
+#:
+#: :Author:    Franz Glasner
+#: :Copyright: (c) 2022-2024 Franz Glasner.
+#:             All rights reserved.
+#: :License:   BSD 3-Clause "New" or "Revised" License.
+#:             See LICENSE for details.
+#:             If you cannot find LICENSE see
+#:             <https://opensource.org/licenses/BSD-3-Clause>
+#: :ID:        @(#)@@SIMPLEVERSIONTAG@@
+#:
+
+set -eu
+
+VERSION="@@VERSION@@"
+
+USAGE='
+USAGE: fzfs [ OPTIONS ] COMMAND [ COMMAND OPTIONS ] [ ARG ... ]
+
+OPTIONS:
+
+  -V    Print the program name and version number to stdout and exit
+
+  -h    Print this help message to stdout and exit
+
+COMMANDS:
+
+  copy-tree [-A] [-M MOUNTPOINT] [-n] [-u] SOURCE-DATASET DEST-DATASET
+
+  create-tree [-A] [-M MOUNTPOINT] [-n] [-p] [-u] SOURCE-DATASET DEST-DATASET
+
+  mount [-O] [-N] [-P] [-u] [-n] DATASET [MOUNTPOINT]
+
+  umount DATASET
+
+  unmount
+
+'
+
+
+_p_datadir="$(dirname "$0")"/../share/local-bsdtools
+. "${_p_datadir}/common.subr"
+
+
+#
+#: Implementation of the "mount" command.
+#:
+#: Mount a dataset and recursively all its children datasets.
+#:
+command_mount() {
+    local _dsname _mountpoint
+    local _opt_dry_run _opt_mount_outside _opt_mount_natural
+    local _opt_mount_children_only
+
+    local _name _mp _canmount _mounted _rootds_mountpoint _relative_mp _real_mp
+
+    _opt_dry_run=""
+    _opt_mount_outside=""
+    _opt_mount_natural=""
+    _opt_mount_children_only=""
+    while getopts "ONPnu" _opt ; do
+        case ${_opt} in
+            O)
+                _opt_mount_outside="yes"
+                ;;
+            N)
+                _opt_mount_natural="yes"
+                ;;
+            P)
+                _opt_mount_children_only="yes"
+                ;;
+            n|u)
+                _opt_dry_run="yes"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    _dsname="${1-}"
+    _mountpoint="${2-}"
+
+    if [ -z "${_dsname}" ]; then
+        echo "ERROR: no dataset given" >&2
+        return 2
+    fi
+
+    _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")"  || \
+        { echo "ERROR: root dataset does not exist" >&2; return 1; }
+
+    if [ -z "${_mountpoint}" ]; then
+        if [ "${_opt_mount_natural}" = "yes" ]; then
+            _mountpoint="${_rootds_mountpoint}"
+        else
+            echo "ERROR: no mountpoint given" >&2
+            return 2
+        fi
+    else
+        if [ "${_opt_mount_natural}" = "yes" ]; then
+            echo "ERROR: Cannot have a custom mountpoint when \"-O\" is given" >&2
+            return 2
+        fi
+    fi
+
+    # Eventually remove a trailing slash
+    _mountpoint="${_mountpoint%/}"
+    if [ -z "${_mountpoint}" ]; then
+        echo "ERROR: would mount over the root filesystem" >&2
+        return 1
+    fi
+
+    zfs list -H -o name,mountpoint,canmount,mounted -s mountpoint -t filesystem -r "${_dsname}" \
+    | {
+        while IFS=$'\t' read -r _name _mp _canmount _mounted ; do
+            # Skip filesystems that are already mounted
+            [ "${_mounted}" = "yes" ] && continue
+            # Skip filesystems that must not be mounted
+            [ "${_canmount}" = "off" ] && continue
+            #
+            # Mount only the children and skip the given parent dataset
+            # if required
+            #
+            [ \( "${_opt_mount_children_only}" = "yes" \) -a \( "${_name}" = "${_dsname}" \) ] && continue
+            case "${_mp}" in
+                "none"|"legacy")
+                    # Do nothing for filesystem with unset or legacy mountpoints
+                    ;;
+                "${_rootds_mountpoint}"|"${_rootds_mountpoint}/"*)
+                    #
+                    # Handle only mountpoints that have a mountpoint below
+                    # the parent datasets mountpoint
+                    #
+
+                    # Determine the mountpoint relative to the parent mountpoint
+                    _relative_mp="${_mp#${_rootds_mountpoint}}"
+                    # Eventually remove a trailing slash
+                    _relative_mp="${_relative_mp%/}"
+                    # The real effective full mountpoint
+                    _real_mp="${_mountpoint}${_relative_mp}"
+
+                    #
+                    # Consistency and sanity check: computed real mountpoint must
+                    # be equal to the configured mountpoint when no custom mountpoint
+                    # is given.
+                    #
+                    if [ "${_opt_mount_natural}" = "yes" ]; then
+                        if [ "${_real_mp}" != "${_mp}" ]; then
+                            echo "ERROR: mountpoint mismatch" >&2
+                            return 1
+                        fi
+                    fi
+
+                    if [ "${_opt_dry_run}" = "yes" ]; then
+                        echo "Would mount ${_name} on ${_real_mp}"
+                    else
+                        mkdir -p "${_real_mp}" 1> /dev/null 2> /dev/null || \
+                            { echo "ERROR: cannot create mountpoint ${_real_mp}" >&2; return 1; }
+                        echo "Mounting ${_name} on ${_real_mp}"
+                        mount -t zfs "${_name}" "${_real_mp}" || return 1
+                    fi
+                    ;;
+                *)
+                    if [ "${_opt_mount_outside}" = "yes" ]; then
+                        if [ "${_opt_dry_run}" = "yes" ]; then
+                            echo "Would mount ${_name} on configured ZFS dataset mountpoint ${_mp}"
+                        else
+                            echo "Mounting ${_name} on configured ZFS dataset mountpoint ${_mp}"
+                            zfs mount "${_name}" || return 1
+                        fi
+                    else
+                        echo "Skipping ${_name} because its configured ZFS mountpoint is not relative to given root dataset" 2>&1
+                    fi
+                    ;;
+            esac
+        done
+
+        return 0
+    }
+}
+
+
+#:
+#: Implement the "umount" command.
+#:
+#: Umount a datasets and recursively all its children datasets.
+#:
+command_umount() {
+    local _dsname
+
+    local _name _mp _rest _rootds_mountpoint
+
+    _dsname="${1-}"
+    [ -z "${_dsname}" ] && { echo "ERROR: no dataset given" 1>&2; return 2; }
+
+    # Just determine whether the given dataset name exists
+    _rootds_mountpoint="$(zfs list -H -o mountpoint -t filesystem "${_dsname}")" || { echo "ERROR: dataset not found" 1>&2; return 1; }
+
+    mount -t zfs -p \
+    | grep -E "^${_dsname}(/|\s)" \
+    | sort -n -r \
+    | {
+        while IFS=' '$'\t' read -r _name _mp _rest ; do
+            echo "Umounting ${_name} on ${_mp}"
+            umount "${_mp}" || return 1
+        done
+    }
+    return 0
+}
+
+
+#:
+#: Implementation of "copy-tree"
+#:
+command_copy_tree() {
+    local _ds_source _ds_target
+    local _opt_mountpoint _opt_mount_noauto _opt_nomount _opt_keep _opt_dry_run
+
+    local _ds_source_base _ds_source_snapshot _snapshot_suffix
+    local _ds_tree _ds _ds_relname _ds_canmount
+    local _arg_canmount _arg_mp1 _arg_mp2
+
+    _opt_mountpoint=""
+    _opt_mount_noauto=""
+    _opt_nomount=""
+    _opt_keep=""
+    _opt_dry_run=""
+
+    while getopts "AM:knu" _opt ; do
+        case ${_opt} in
+            A)
+                _opt_mount_noauto="-o canmount=noauto"
+                ;;
+            M)
+                _opt_mountpoint="${OPTARG}"
+                ;;
+            k)
+                _opt_keep="yes"
+                ;;
+            n)
+                _opt_dry_run="-n"
+                ;;
+            u)
+                _opt_nomount="-u"
+                ;;
+            \?)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    _ds_source="${1-}"
+    _ds_target="${2-}"
+
+    [ -z "${_ds_source}" ] && { echo "ERROR: no source given" 1>&2; return 2; }
+    [ -z "${_ds_target}" ] && { echo "ERROR: no target name given" 1>&2; return 2; }
+
+    if ! zfs get -H name "${_ds_source}" >/dev/null 2>&1; then
+        echo "ERROR: source dataset does not exist: ${_ds_source}" 1>&2;
+        return 1;
+    fi
+    if zfs get -H name "${_ds_target}" >/dev/null 2>&1; then
+        echo "ERROR: target dataset already exists: ${_ds_target}" 1>&2;
+        return 1;
+    fi
+
+    _ds_source_base="${_ds_source%@*}"
+    _ds_source_snapshot="${_ds_source##*@}"
+    _snapshot_suffix="@${_ds_source_snapshot}"
+
+    if [ "${_ds_source_snapshot}" = "${_ds_source_base}" ]; then
+        # No snapshot given
+        [ "${_ds_source_base}" = "${_ds_source}" ] || { echo "ERROR:" 1>&2; return 1; }
+        _ds_source_snapshot=""
+        _snapshot_suffix=""
+    fi
+
+    _ds_tree=""
+
+    while IFS=$'\n' read -r _ds; do
+        if [ -z "${_ds_tree}" ]; then
+            _ds_tree="${_ds}"
+        else
+            _ds_tree="${_ds_tree}"$'\n'"${_ds}"
+        fi
+    done <<EOF20ee7ea0781414fab8c305d3875d15e
+$(zfs list -H -r -t filesystem -o name -s name "${_ds_source_base}")
+EOF20ee7ea0781414fab8c305d3875d15e
+    # Check the existence of all intermediate datasets and their shapshots
+    IFS=$'\n'
+    for _ds in ${_ds_tree}; do
+        if  ! zfs get -H name "${_ds}${_snapshot_suffix}" >/dev/null 2>&1; then
+            echo "ERROR: child dataset does not exist: ${_ds}${_snapshot_suffix}" 1>&2
+            return 1
+        fi
+    done
+    IFS=$'\n'
+    for _ds in ${_ds_tree}; do
+        # Reset IFS
+        IFS=$' \t\n'
+
+        # Determine the relative name of the dataset
+        _ds_relname="${_ds#${_ds_source_base}}"
+
+        _ds_canmount=$(zfs get -H -o value canmount "${_ds}")
+
+        if [ "${_ds_canmount}" = "off" ]; then
+            _arg_canmount="-o canmount=off"
+        else
+            _arg_canmount="${_opt_mount_noauto}"
+        fi
+
+        if [ -z "${_ds_relname}" ]; then
+            #
+            # Source root to target root
+            #
+            if [ -z "${_opt_mountpoint}" ]; then
+                _arg_mp1=-x
+                _arg_mp2=mountpoint
+            else
+                _arg_mp1=-o
+                _arg_mp2="mountpoint=${_opt_mountpoint}"
+            fi
+        else
+            _arg_mp1=-x
+            _arg_mp2=mountpoint
+        fi
+        if [ -z "${_opt_dry_run}" ]; then
+            zfs send -Lec -p -v "${_ds}${_snapshot_suffix}" | zfs receive -v ${_opt_nomount} ${_arg_canmount} ${_arg_mp1} "${_arg_mp2}" "${_ds_target}${_ds_relname}"
+        else
+            echo "Would execute: zfs send -Lec -p -v '${_ds}${_snapshot_suffix}' | zfs receive -v ${_opt_nomount} ${_arg_canmount} ${_arg_mp1} '${_arg_mp2}' '${_ds_target}${_ds_relname}'"
+        fi
+        # for the loop
+        IFS=$'\n'
+    done
+    # Reset to default
+    IFS=$' \t\n'
+    # Remove received snapshots by default
+    if [ -n "${_ds_source_snapshot}" ]; then
+        if [ -z "${_opt_keep}" ]; then
+            if [ -z "${_opt_dry_run}" ]; then
+                zfs destroy -rv "${_ds_target}${_snapshot_suffix}"
+            else
+                echo "Would execute: zfs destroy -rv '${_ds_target}${_snapshot_suffix}'"
+            fi
+        fi
+    fi
+}
+
+
+#:
+#: Implement the "create-tree" command
+#:
+#: Create a ZFS dataset tree from a source tree tree including important properties
+#:
+command_create_tree() {
+    local _source_ds _target_ds
+    local _opt_dry_run _opt_mountpoint _opt_noauto _opt_nomount _opt_canmount_parent_only
+
+    local _opt _source_name _snapshot_name _current_name _current_type _relative_name _zfs_opts _zfs_canmount _zfs_mountpoint
+
+    _opt_noauto=""
+    _opt_dry_run=""
+    _opt_mountpoint=""
+    _opt_nomount=""
+    _opt_canmount_parent_only=""
+
+    while getopts "AM:npu" _opt ; do
+        case ${_opt} in
+            A)
+                _opt_noauto="-o canmount=noauto"
+                ;;
+            M)
+                _opt_mountpoint="${OPTARG}"
+                ;;
+            n)
+                _opt_dry_run="yes"
+                ;;
+            p)
+                _opt_canmount_parent_only="yes"
+                ;;
+            u)
+                _opt_nomount="-u"
+                ;;
+            \?|:)
+                return 2;
+                ;;
+        esac
+    done
+    shift $((OPTIND-1))
+    OPTIND=1
+
+    _source_ds="${1-}"
+    _target_ds="${2-}"
+
+    [ -z "${_source_ds}" ] && { echo "ERROR: no source dataset given" 1>&2; return 2; }
+    [ -z "${_target_ds}" ] && { echo "ERROR: no destination dataset given" 1>&2; return 2; }
+
+    _source_name="${_source_ds%@*}"
+    if [ "${_source_name}" != "${_source_ds}" ]; then
+        _snapshot_name="${_source_ds##*@}"
+    else
+        _snapshot_name=""
+    fi
+
+    [ -n "${_snapshot_name}" ] && { echo "ERROR: No snapshot sources are supported yet" 1>&2; return 2; }
+
+    zfs list -H -r -t filesystem -o name,type "${_source_ds}" \
+    | {
+        while IFS=$'\t' read -r _current_name _current_type ; do
+            # Determine the relative name of the dataset
+            _relative_name="${_current_name#${_source_name}}"
+            _relative_name="${_relative_name%@*}"
+            if [ "${_current_type}" != "filesystem" ]; then
+                echo "ERROR: Got a snapshot but expected a filesystem" 1>&2
+                return 1
+            fi
+            _zfs_opts="$(_get_local_zfs_properties_for_create "${_current_name}" atime exec setuid compression primarycache sync readonly)"
+            if [ -z "${_relative_name}" ]; then
+                #
+                # Root
+                #
+                if [ -z "${_opt_noauto}" ]; then
+                    _zfs_canmount="$(_get_local_zfs_properties_for_create "${_current_name}" canmount)"
+                else
+                    _zfs_canmount="${_opt_noauto}"
+                fi
+                if [ -n "${_opt_mountpoint}" ]; then
+                    _zfs_mountpoint="${_opt_mountpoint}"
+                else
+                    _zfs_mountpoint=""
+                fi
+
+                if [ "${_opt_dry_run}" = "yes" ]; then
+                    if [ -z "${_zfs_mountpoint}" ]; then
+                        echo "Would call: zfs create ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} ${_target_ds}${_relative_name}"
+                    else
+                        echo "Would call: zfs create ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} -o mountpoint=${_zfs_mountpoint} ${_target_ds}${_relative_name}"
+                    fi
+                else
+                    if [ -z "${_zfs_mountpoint}" ]; then
+                        zfs create -v ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} "${_target_ds}${_relative_name}"
+                    else
+                        zfs create -v ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} -o "mountpoint=${_zfs_mountpoint}" "${_target_ds}${_relative_name}"
+                    fi
+                fi
+            else
+                #
+                # Children
+                #
+                if [ -z "${_opt_noauto}" ]; then
+                    if [ "${_opt_canmount_parent_only}" = "yes" ]; then
+                        _zfs_canmount=""
+                    else
+                        _zfs_canmount="$(_get_local_zfs_properties_for_create "${_current_name}" canmount)"
+                    fi
+                else
+                    _zfs_canmount="${_opt_noauto}"
+                fi
+                if [ "${_opt_dry_run}" = "yes" ]; then
+                    echo "Would call: zfs create ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} ${_target_ds}${_relative_name}"
+                else
+                    zfs create -v ${_opt_nomount} ${_zfs_opts} ${_zfs_canmount} "${_target_ds}${_relative_name}"
+                fi
+            fi
+        done
+    }
+    return 0
+}
+
+
+#
+# Global option handling
+#
+while getopts "Vh" _opt ; do
+    case ${_opt} in
+        V)
+            printf 'fzfs %s\n' '@@SIMPLEVERSIONSTR@@'
+            exit 0
+            ;;
+        h)
+            echo "${USAGE}"
+            exit 0
+            ;;
+        \?)
+            exit 2;
+            ;;
+        *)
+            echo "ERROR: option handling failed" 1>&2
+            exit 2
+            ;;
+    esac
+done
+#
+# Reset the Shell's option handling system to prepare for handling
+# command-local options.
+#
+shift $((OPTIND-1))
+OPTIND=1
+
+test $# -gt 0 || { echo "ERROR: no command given" 1>&2; exit 2; }
+
+command="$1"
+shift
+
+case "${command}" in
+    mount)
+        command_mount "$@"
+        ;;
+    umount|unmount)
+        command_umount "$@"
+        ;;
+    copy-tree)
+        command_copy_tree "$@"
+        ;;
+    create-tree)
+        command_create_tree "$@"
+        ;;
+    *)
+        echo "ERROR: unknown command \`${command}'" 1>&2
+        exit 2
+        ;;
+esac
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/examples/local-bsdtools/freebsd-update-ftjail-template.sh	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+set -e
+set -x
+set -u
+
+BASE_RO=dpool/jail/ttmpl/base-ro/13.2-RELEASE
+SKEL_RW=dpool/jail/ttmpl/skel-rw/13.2-RELEASE
+
+NEW_VER="p5"
+
+MOUNTPOINT="/var/tmp/13.2"
+
+ftjail mount-tmpl -P "$BASE_RO" "$SKEL_RW" "$MOUNTPOINT"
+
+freebsd-update -b "$MOUNTPOINT" fetch 
+freebsd-update -b "$MOUNTPOINT" install
+
+ftjail snapshot-tmpl "$BASE_RO" "$SKEL_RW" "$NEW_VER"
+
+ftjail build-etcupdate-current-tmpl "$MOUNTPOINT" /jail/RELEASE/etcupdate-current-13.2@${NEW_VER}.tbz
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/examples/local-bsdtools/freebsd-update-ftjail.sh	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,52 @@
+#!/bin/sh
+
+set -e
+set -x
+set -u
+
+JAIL_NAME="${1:-}"
+
+if [ -z "${JAIL_NAME}" ]; then
+  echo "ERROR: No jail name given" 1>&2
+  exit 2
+fi
+
+
+
+OLD_BASE_RO="dpool/jail/ttmpl/base-ro/13.2-RELEASE@p5"
+NEW_BASE_RO="dpool/jail/ttmpl/base-ro/13.2-RELEASE@p9"
+NEW_ETCUPDATE="/jail/RELEASE/etcupdate-current-13.2@p9.tbz"
+
+SNAPSHOT_NAME="13.2-p5-20240108-1"
+SNAPSHOT_ROOT="dpool/jail/TVAR"
+
+BASE_DIR="/jail/TROOT"
+
+case "${JAIL_NAME}" in
+  ftp)
+    zfs umount dpool/data/ftp/home-data
+    ;;
+  pg14)
+    zfs umount dpool/pg/walarchive-db1-pg14
+    zfs umount dpool/pg/data-db1-pg14
+    ;;
+  *)
+    ;;
+esac
+
+zfs snapshot -r "${SNAPSHOT_ROOT}/${JAIL_NAME}@${SNAPSHOT_NAME}"
+
+ftjail freebsd-update -o "${OLD_BASE_RO}" "${BASE_DIR}/${JAIL_NAME}" "${NEW_BASE_RO}" "${NEW_ETCUPDATE}"
+
+case "${JAIL_NAME}" in
+  ftp)
+    zfs mount dpool/data/ftp/home-data
+    ;;
+  pg14)
+    zfs mount dpool/pg/data-db1-pg14
+    zfs mount dpool/pg/walarchive-db1-pg14
+    ;;
+  *)
+    ;;
+esac
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/local-bsdtools/common.subr	Fri Jun 14 08:30:35 2024 +0200
@@ -0,0 +1,232 @@
+# -*- mode: shell-script; indent-tabs-mode: nil; -*-
+#:
+#: :Author:    Franz Glasner
+#: :Copyright: (c) 2017-2024 Franz Glasner.
+#:             All rights reserved.
+#: :License:   BSD 3-Clause "New" or "Revised" License.
+#:             See LICENSE for details.
+#:             If you cannot find LICENSE see
+#:             <https://opensource.org/licenses/BSD-3-Clause>
+#: :ID:        @(#)@@SIMPLEVERSIONTAG@@
+#:
+
+
+#:
+#: Ensure that no command line options are given
+#:
+#: Args:
+#:   $@:
+#:
+#: Exit:
+#:   2: If formally `getopts` finds options in "$@"
+#:
+#: Return:
+#:   0
+#:
+_ensure_no_options() {
+    local _opt
+
+    while getopts ":" _opt ; do
+        [ "${_opt}" = '?' ] && { echo "ERROR: no option allowed" 1>&2; exit 2; }
+    done
+    return 0
+}
+
+
+#:
+#: Determine some important dataset properties that are set locally
+#:
+#: Args:
+#:   $1: the dataset
+#:   $2 ...: the properties to check for
+#:
+#: Output (stdout):
+#:   An option string suited for use in "zfs create"
+#:
+_get_local_zfs_properties_for_create() {
+    local ds
+
+    local _res _prop _value _source
+
+    ds="${1}"
+    shift
+
+    _res=""
+
+    for _prop in "$@" ; do
+        IFS=$'\t' read -r _value _source <<EOF73GHASGJKKJ354
+$(zfs get -H -p -o value,source "${_prop}" "${ds}")
+EOF73GHASGJKKJ354
+        case "${_source}" in
+            local)
+                if [ -z "${_res}" ]; then
+                    _res="-o ${_prop}=${_value}"
+                else
+                    _res="${_res} -o ${_prop}=${_value}"
+                fi
+                ;;
+            *)
+                # VOID
+                ;;
+        esac
+    done
+    echo "${_res}"
+}
+
+
+#:
+#: Use `mount -t zfs -p` to determine the ZFS dataset for a given mountpoint
+#:
+#: Args:
+#:  $1: the mountpoint
+#:
+#: Output (stdout):
+#:   The name of the ZFS dataset that is mounted on `$1`
+#:
+#: Return:
+#:   0: if a mounted dataset is found
+#:   1: if no ZFS dataset is found that is mounted on `$1`
+#:
+#: The dataset has to be mounted.
+#:
+_get_zfs_dataset_for_mountpoint() {
+    local _mountpoint
+
+    local _ds _mount _rest
+
+    _mountpoint="$1"
+
+    mount -t zfs -p \
+    | {
+        while IFS=' '$'\t' read -r _ds _mount _rest ; do
+            if [ "$_mount" = "$_mountpoint" ]; then
+                echo "${_ds}"
+                return 0
+            fi
+        done
+        return 1
+    }
+}
+
+
+#:
+#: Determine the ZFS dataset that is mounted at `$1`/var/empty.
+#:
+#: Allow special handling for <mountpoint>/var/empty which may be
+#: mounted read-only.
+#:
+#: Args:
+#:  $1: the root mountpoint
+#:
+#: Output (stdout):
+#:   The name of the ZFS dataset that is mounted on `$1`/var/empty
+#:
+#: Return:
+#:   0: if a mounted dataset is found
+#:   1: if no ZFS dataset is found that is mounted on `$1`
+#:
+#: The dataset has to be mounted.
+#:
+_get_zfs_dataset_for_varempty() {
+    local _mountpoint
+
+    local _ve_mount
+
+    _mountpoint="$1"
+
+    if [ "$_mountpoint" = '/' ]; then
+        _ve_mount='/var/empty'
+    else
+        _ve_mount="${_mountpoint}/var/empty"
+    fi
+
+    _get_zfs_dataset_for_mountpoint "${_ve_mount}"
+}
+
+
+#:
+#: Search for a running jail where it's "path" points to a given location
+#:
+#: Args:
+#:   $1: the location to search for
+#:
+#: Output (stdout):
+#:   The name if the jail with a "path" that is equal to the input param.
+#:   Nothing if a jail is not found.
+#:
+#: Return:
+#:   0: if a running jail is found
+#:   1: error
+#:   2: jail found but currently dying
+#:   3: no running jail found
+#:
+_get_jail_from_path() {
+  local _location
+
+  local _name _path _dying
+
+  _location="${1-}"
+  [ -z "${_location}" ] && { echo "ERROR: no mountpoint given" 1>&2; return 1; }
+
+
+  jls -d name path dying \
+  | {
+      while IFS=' '$'\t' read -r _name _path _dying ; do
+        if [ "${_path}" = "${_location}" ]; then
+          if [ "${_dying}" != "false" ]; then
+            echo "Jail \`${_name}' is currently dying" 1>&2
+            return 2
+          fi
+          echo "${_name}"
+          return 0
+        fi
+      done
+      return 3
+  }
+}
+
+
+#:
+#: Search for mounts and sub-mounts at a given directory.
+#:
+#: The output is sorted by the mountpoint.
+#:
+#: Args:
+#:   $1: the directory where to start for mounts and sub-mounts
+#:
+#: Output (stdout):
+#:   The sorted list (lines) of mounts in :manpage:`fstab(5)` format.
+#:   This list may be empty.
+#:
+#: Exit:
+#:   1: on fatal errors (usage et al.)
+#:
+#: Important:
+#:   The input directory **must** be an absolute path.
+#:
+_get_mounts_at_directory() {
+    local _directory
+
+    local _fstab
+
+    _directory=${1-}
+    case "${_directory}" in
+        */)
+            echo "ERROR: a trailing slash in directory name given" 1>&2;
+            exit 1;
+            ;;
+        /*)
+            :
+            ;;
+        '')
+            echo "ERROR: no directory given" 1>&2;
+            exit 1;
+            ;;
+        *)
+            echo "ERROR: directory must be an absolute path" 1>&2;
+            exit 1;
+            ;;
+    esac
+    _fstab="$(mount -p | awk -v pa1="^${_directory}\$" -v pa2="^${_directory}/" '($2 ~ pa1) || ($2 ~ pa2 ) { print; }' | sort -k3)"
+    echo "${_fstab}"
+}
--- a/usr/local/etc/rc.d/bhyve	Thu Jun 13 17:30:24 2024 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-#!/bin/sh
-
-# PROVIDE: bhyve
-# REQUIRE: LOGIN
-# KEYWORD: nojail
-#
-
-#
-# Add the following lines to /etc/rc.conf to enable bhyve:
-# bhyve_enable (bool):  Set to "NO" by default.
-#                       Set it to "YES" to enable bhyve
-# bhyve_profiles (str): Set to "" by default.
-#                       Define your profiles here.
-# bhyve_tapdev (str):   Set to "tap0" by default.
-#                       Set to the tap(4) device to use.
-# bhyve_diskdev (str):  Must be set, no default.
-#                       Set to the disk device to use.
-# bhyve_ncpu (int):     Set to 1 by default.
-#                       Set to the number of CPUs for the VM.
-# bhyve_memsize (int):  Set to 512 by default.
-#                       Set to the number of MB of memory for the VM.
-
-. /etc/rc.subr
-
-name="bhyve"
-rcvar=bhyve_enable
-
-start_precmd="bhyve_prestart"
-status_cmd="bhyve_status"
-poll_cmd="bhyve_poll"
-stop_cmd="bhyve_stop"
-_session=$name
-command="/usr/local/bin/tmux"
-procname="-sh"
-
-[ -z "$bhyve_enable" ]  && bhyve_enable="NO"
-[ -z "$bhyve_tapdev" ]  && bhyve_tapdev="tap0"
-[ -z "$bhyve_diskdev" ] && bhyve_diskdev="none"
-[ -z "$bhyve_ncpu" ]    && bhyve_ncpu="1"
-[ -z "$bhyve_memsize" ] && bhyve_memsize="512"
-
-load_rc_config $name
-
-if [ -n "$2" ]; then
-	profile="$2"
-	_session="${_session}_${profile}"
-	if [ "x${bhyve_profiles}" != "x" ]; then
-		eval bhyve_enable="\${${_session}_enable:-${bhyve_enable}}"
-		eval bhyve_tapdev="\${${_session}_tapdev:-${bhyve_tapdev}}"
-		eval bhyve_diskdev="\${${_session}_diskdev:-${bhyve_diskdev}}"
-		eval bhyve_ncpu="\${${_session}_ncpu:-${bhyve_ncpu}}"
-		eval bhyve_memsize="\${${_session}_memsize:-${bhyve_memsize}}"
-	else
-		echo "$0: extra argument ignored"
-	fi
-else
-	if [ "x${bhyve_profiles}" != "x" -a "x$1" != "x" ]; then
-		for profile in ${bhyve_profiles}; do
-			eval _enable="\${bhyve_${profile}_enable}"
-			case "x${_enable:-${bhyve_enable}}" in
-			x|x[Nn][Oo]|x[Nn][Oo][Nn][Ee])
-				continue
-				;;
-			x[Yy][Ee][Ss])
-				;;
-			*)
-				if test -z "$_enable"; then
-					_var=bhyve_enable
-				else
-					_var=bhyve_"${profile}"_enable
-				fi
-				echo "Bad value" \
-				    "'${_enable:-${bhyve_enable}}'" \
-				    "for ${_var}. " \
-				    "Profile ${profile} skipped."
-				continue
-				;;
-			esac
-			echo "===> bhyve profile: ${profile}"
-			/usr/local/etc/rc.d/bhyve $1 ${profile}
-			retcode="$?"
-			if [ "0${retcode}" -ne 0 ]; then
-				failed="${profile} (${retcode}) ${failed:-}"
-			else
-				success="${profile} ${success:-}"
-			fi
-		done
-		exit 0
-	fi
-	profile=$name
-fi
-
-pidfile="/var/run/${_session}.pid"
-
-
-bhyve_prestart()
-{
-	case ${bhyve_diskdev} in
-	[Nn][Oo][Nn][Ee] | '')
-		echo "No ${_session}_diskdev set. Quitting." 1>&2
-		return 1;
-		;;
-	esac
-	if [ ! -c "${bhyve_diskdev}" -a ! -f "${bhyve_diskdev}" ]; then
-		echo "${bhyve_diskdev} doesn't exist or is not suitable as a diskdev" 1>&2
-		return 1;
-	fi
-}
-
-bhyve_status()
-{
-	if ${command} has-session -t ${_session} 2>/dev/null; then
-		echo "${_session} is running."
-	else
-		echo "${_session} is not running."
-		return 1
-	fi
-}
-
-bhyve_poll()
-{
-	echo -n "Waiting for session: ${_session}"
-	while ${command} has-session -t ${_session} 2>/dev/null; do
-		sleep 1
-	done
-	echo
-}
-
-bhyve_stop()
-{
-	if ${command} has-session -t ${_session} 2>/dev/null; then
-		echo "Stopping ${_session}."
-		${command} kill-session -t ${_session}
-		while ${command} has-session -t ${_session} 2>/dev/null; do
-			sleep 1
-		done
-	fi
-	rm -f ${pidfile}
-}
-
-command_args="new-session -ds ${_session} \"sh -c 'echo \\\$PPID >${pidfile}; while true; do /usr/sbin/bhyvectl --vm=${_session} --destroy; /usr/sbin/bhyveload -m ${bhyve_memsize} -d ${bhyve_diskdev} ${_session} && /usr/sbin/bhyve -c ${bhyve_ncpu} -m ${bhyve_memsize} -AI -H -P -g 0 -s 0:0,hostbridge -s 1:0,virtio-net,${bhyve_tapdev} -s 2:0,virtio-blk,${bhyve_diskdev} -s 31,lpc -l com1,stdio ${_session} || break; done'\""
-
-run_rc_command "$1"