Mercurial > hgrepos > FreeBSD > ports > sysutils > local-bsdtools
changeset 355:f1c8fc3af3e1
MERGE/RENAME: Copy the original 800.scrub-zfs into 750.local-trim-zfs.
This is to prepare for a periodic TRIM of SSD drives within ZFS pools.
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Thu, 08 Dec 2022 00:54:18 +0100 |
| parents | d71f9efac6a8 (diff) a7dfa074dae1 (current diff) |
| children | 2ba1072103f1 |
| files | etc/periodic/daily/750.local-trim-zfs etc/periodic/daily/800.scrub-zfs |
| diffstat | 46 files changed, 5425 insertions(+), 110 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,6 @@ +syntax: regexp + +^work/ +^docs/_build/ +^docs/_venv.*/ +^_venv.*/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgsigs Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,21 @@ +3c2277cd59064674727d562557f9dd3d766b769b 0 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgtags Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,26 @@ +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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,92 @@ +# Created by: Franz Glasner <freebsd-dev@dom66.de> + +PORTNAME= local-bsdtools +PORTVERSION= 0.20 +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 + +.include <bsd.port.options.mk> + +SRC= ${.CURDIR} + +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]} + +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 -e "s|\\\$$Date\\\$$|\$$Date: ${HGDATE} \$$|" ${WRKSRC}/${_rp} + ${SED} -i "" -E -e "s|\\\$$Revision\\\$$|\$$Revision: ${HGREVISION} \$$|" ${WRKSRC}/${_rp} + ${SED} -i "" -E -e "s|\\\$$Author\\\$$|\$$Author: ${HGAUTHOR} \$$|" ${WRKSRC}/${_rp} + ${SED} -i "" -e "s|\\\$$HGpath\\\$$|\$$HGpath: ${HGPATH} \$$|" ${WRKSRC}/${_rp} + ${SED} -i "" -e "s|\\\$$HGsource\\\$$|\$$HGsource: ${HGPATH}/${_rp} \$$|" ${WRKSRC}/${_rp} + ${SED} -i "" -e "s|\\\$$Header\\\$$|\$$Header: ${_rp} ${HGREVISION} ${HGDATE} ${HGAUTHOR} ${HGPHASE} \$$|" ${WRKSRC}/${_rp} + ${SED} -i "" -e "s|\\\$$HGid\\\$$|\$$HGid: ${HGPATH}/${_rp} ${HGREVISION} ${HGDATE} ${HGAUTHOR} ${HGPHASE} \$$|" ${WRKSRC}/${_rp} + ${SED} -i "" -e "s|@@VERSION@@|${PORTVERSION}|" ${WRKSRC}/${_rp} + ${SED} -i "" -e "s|@@HGREVISION@@|${HGREVISION}|" ${WRKSRC}/${_rp} + ${SED} -i "" -e "s|@@ETCDIR@@|${ETCDIR}|" ${WRKSRC}/${_rp} + ${SED} -i "" -e "s|@@PKGORIGIN@@|${PKGORIGIN}|" ${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 + ${CP} -v ${SRC}/${_ef} ${WRKSRC}/${_ef} + ${SED} -i "" -e "s|\\\$$HGid\\\$$|\$$HGid: ${HGPATH}/${_ef} ${HGREVISION} ${HGDATE} ${HGAUTHOR} ${HGPHASE} \$$|" ${WRKSRC}/${_ef} + ${SED} -i "" -e "s|@@PKGORIGIN@@|${PKGORIGIN}|" ${WRKSRC}/${_ef} +.endfor + +post-extract-DOCS-on: + ${MKDIR} ${WRKSRC}/docs + (${TAR} -C ${.CURDIR}/docs -c --exclude ./_build -f - . | ${TAR} -C ${WRKSRC}/docs -x -f - ) + +.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 + ${INSTALL_SCRIPT} ${WRKSRC}/etc/periodic/daily/${_ps} ${STAGEDIR}${PREFIX}/etc/periodic/daily +.endfor + +post-install-DOCS-on: +.for _mp in ${MANPAGES8:R} + ${INSTALL_DATA} ${WRKSRC}/docs/_build/man/${_mp}.8 ${STAGEDIR}${PREFIX}/man/man8/${_mp}.8 +.endfor + +.include <bsd.port.mk>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/Makefile Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,108 @@ +# 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-2022, 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/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", [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/fzfs", "fzfs", "A ZFS management helper tool", [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 Thu Dec 08 00:54:18 2022 +0100 @@ -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/index8
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/make.bat Thu Dec 08 00:54:18 2022 +0100 @@ -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/index8.rst Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,31 @@ +.. -*- 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/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-mount + man8/fzfs-umount
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/man/man8/bsmtp2dma.rst Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,96 @@ +.. -*- 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 a 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`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/man/man8/check-ports.rst Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,191 @@ +.. -*- 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. + +.. 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. + +.. 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 is:: + + # 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) + # + + +:: + + # 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) + # + +:: + + # 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) + # + +:: + + # 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) + # + + +See Also +-------- + +:manpage:`fpkg(8)`
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/man/man8/fjail-configure.rst Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,61 @@ +.. -*- 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:: -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,47 @@ +.. -*- coding: utf-8; indent-tabs-mode: nil; -*- + +ftjail-populate-tmpl +==================== + +Synopsis +-------- + +**ftjail populate-tmpl** **-L** `directory` `path/to/base.txz` + +**ftjail populate-tmpl** **-P** `directory` `path/to/base.txz` + + +Description +----------- + +Populate the directory in `directory` with a FreeBSD base system. +The base system's archive ist in `path/to/base.txz`. + +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. + + +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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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-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 + +:manpage:`ftjail-copy-skel(8)` + + Copy a ZFS skeleton recursively + + +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/fzfs-mount.rst Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,43 @@ +.. -*- 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 + +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-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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,52 @@ +.. -*- 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 +============== + + +See Also +-------- + +- :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-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:`fzfs(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.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/etc/bsmtp2dma.conf.sample Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,15 @@ +# +# Configure bsmtp2dma tool. +# +# Is is sourced by `bsmtp2dma`. +# +# @(#)@@PKGORIGIN@@ $HGid$ +# + +# +# 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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,7 @@ +# +# Map from installed package name to the (unchanged) parent package name +# +# @(#)@@PKGORIGIN@@ $HGid$ +# +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/750.local-trim-zfs Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,110 @@ +#!/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/periodic/daily/800.local-ipv6-refresh Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,63 @@ +#!/bin/sh +# +# @(#)@@PKGORIGIN@@ $HGid$ +# +# 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 Dec 08 00:50:15 2022 +0100 +++ /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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,3 @@ +# Shell (sh) script to configure tools in local-bsdtools +# @(#)@@PKGORIGIN@@ $HGid$ +#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg-descr Thu Dec 08 00:54:18 2022 +0100 @@ -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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,34 @@ +@comment FILES +etc/periodic/daily/800.local-ipv6-refresh +sbin/bsmtp2dma +sbin/check-ports +sbin/fjail +sbin/ftjail +sbin/fpkg +sbin/fzfs +%%DOCS%%man/man8/local-bsdtools.8.gz +%%DOCS%%man/man8/bsmtp2dma.8.gz +%%DOCS%%man/man8/check-ports.8.gz +%%DOCS%%man/man8/fjail.8.gz +%%DOCS%%man/man8/fjail-configure.8.gz +%%DOCS%%man/man8/fjail-freebsd-update.8.gz +%%DOCS%%man/man8/fjail-hostid.8.gz +%%DOCS%%man/man8/fpkg.8.gz +%%DOCS%%man/man8/ftjail.8.gz +%%DOCS%%man/man8/ftjail-build-etcupdate-current-tmpl.8.gz +%%DOCS%%man/man8/ftjail-copy-skel.8.gz +%%DOCS%%man/man8/ftjail-datasets-tmpl.8.gz +%%DOCS%%man/man8/ftjail-freebsd-update.8.gz +%%DOCS%%man/man8/ftjail-interlink-tmpl.8.gz +%%DOCS%%man/man8/ftjail-mount-tmpl.8.gz +%%DOCS%%man/man8/ftjail-populate-tmpl.8.gz +%%DOCS%%man/man8/ftjail-snapshot-tmpl.8.gz +%%DOCS%%man/man8/ftjail-umount-tmpl.8.gz +%%DOCS%%man/man8/fzfs.8.gz +%%DOCS%%man/man8/fzfs-mount.8.gz +%%DOCS%%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 Thu Dec 08 00:54:18 2022 +0100 @@ -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 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: @(#)@@PKGORIGIN@@ $HGid$ + +' + +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 v%s (rv:%s)\n' "${VERSION}" '@@HGREVISION@@' + 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 Thu Dec 08 00:54:18 2022 +0100 @@ -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-2019 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: @(#)@@PKGORIGIN@@ $HGid$ + +' + +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 v%s (rv:%s)\n' "${VERSION}" '@@HGREVISION@@' + 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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,722 @@ +#!/bin/sh +# -*- indent-tabs-mode: nil; -*- +: 'A very minimal BSD Jail management tool. + +:Author: Franz Glasner +:Copyright: (c) 2019-2022 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: @(#)@@PKGORIGIN@@ $HGid$ + +' + +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. +' + + +# Reset to standard umask +umask 0022 + + +_get_dataset_for_mountpoint() { + : 'Use `mount -t zfs -p` to determine the ZFS dataset for a given 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 + } +} + + +_get_dataset_for_varempty() { + : 'Allow special handling for <mountpoint>/var/empty which may be + mounted read-only. + + ' + local _mountpoint + local _ve_mount + + _mountpoint="$1" + + if [ "$_mountpoint" = '/' ]; then + _ve_mount='/var/empty' + else + _ve_mount="${_mountpoint}/var/empty" + fi + + _get_dataset_for_mountpoint "${_ve_mount}" +} + + +#: +#: 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 + 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 "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}" + + echo "security_status_chkmounts_enable=\"NO\"" >> "${_pcl}" + + 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_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 v%s (rv:%s)\n' "${VERSION}" '@@HGREVISION@@' + 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 Thu Dec 08 00:54:18 2022 +0100 @@ -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-2022 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: @(#)@@PKGORIGIN@@ $HGid$ + +' + +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 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 v%s (rv:%s)\n' "${VERSION}" '@@HGREVISION@@' + 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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,1226 @@ +#!/bin/sh +# -*- indent-tabs-mode: nil; -*- +#: +#: A very minimal BSD Thin Jail management tool. +#: +#: :Author: Franz Glasner +#: :Copyright: (c) 2022 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: @(#)@@PKGORIGIN@@ $HGid$ +#: + +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] [-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. +' + +# Reset to standard umask +umask 0022 + + +#: +#: 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 +} + + +_get_dataset_for_mountpoint() { + : 'Use `mount -t zfs -p` to determine the ZFS dataset for a given 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 + } +} + + +#: +#: 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: no running jail found +#: - 3: jail found but currently dying +#: +_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 3 + fi + echo "${_name}" + return 0 + fi + done + return 2 + } +} + + +#: +#: 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: no directory given" 1>&2; + exit 1; + ;; + *) + echo "ERROR: directory must be an absolute path" 1>&2; + exit 1; + ;; + esac + _fstab="$(mount -p | grep -E '\s'"${_directory}" | sort -k3)" + echo "${_fstab}" +} + + +# +# 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 + # + for _child in etc home root tmp usr/local var ; 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 + + local _opt _dir + + _opt_symlink="" + + while getopts "LP" _opt ; do + case ${_opt} in + L) + _opt_symlink="yes" + ;; + P) + _opt_symlink="no" + ;; + \?) + 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-}" + + 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 + + find "${_mp}/boot" -type f -delete || true +} + + +# +# _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 + + local _opt _name _relative_name _root_canmount + + _opt_symlink="" + _opt_nomount="" + _opt_canmount="-o canmount=on" + _opt_mountpoint="" + + while getopts "ALM:Pu" _opt ; do + case ${_opt} in + A) + _opt_canmount="-o canmount=noauto" + ;; + 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; } + + _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 +#: +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 + + _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 2 ] ; then + if [ ${_res} -ne 0 ] ; then + return ${_res} + else + echo "ERROR: Please stop the \`${_jailname}' jail" >&2 + return 1 + fi + 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 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 v%s (rv:%s)\n' "${VERSION}" '@@HGREVISION@@' + 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 Thu Dec 08 00:54:18 2022 +0100 @@ -0,0 +1,252 @@ +#!/bin/sh +# -*- indent-tabs-mode: nil; -*- +#: +#: A ZFS management helper tool. +#: +#: :Author: Franz Glasner +#: :Copyright: (c) 2022 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: @(#)@@PKGORIGIN@@ $HGid$ +#: + +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: + + mount [-O] [-N] [-P] [-u] [-n] DATASET [MOUNTPOINT] + + umount DATASET + + unmount + +' + +# +#: 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 +} + +# +# Global option handling +# +while getopts "Vh" _opt ; do + case ${_opt} in + V) + printf 'ftjail v%s (rv:%s)\n' "${VERSION}" '@@HGREVISION@@' + 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 "$@" + ;; + *) + echo "ERROR: unknown command \`${command}'" 1>&2 + exit 2 + ;; +esac
