diff sbin/fjail @ 128:3dcae0e91769

Move all admin scripts into the "sbin" folder
author Franz Glasner <fzglas.hg@dom66.de>
date Thu, 17 Oct 2019 09:09:13 +0200
parents bin/fjail@52523a19797e
children 6be3742d21f7
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sbin/fjail	Thu Oct 17 09:09:13 2019 +0200
@@ -0,0 +1,304 @@
+#!/bin/sh
+# -*- indent-tabs-mode: nil; -*-
+: 'A very minimal BSD Jail management tool.
+
+: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$
+
+'
+
+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 [-u] PARENT CHILD
+
+    Create ZFS datasets to be used within a jail
+
+    PARENT must exist already and CHILD must not exist.
+
+    -u        Do not automatically mount newly created datasets
+
+  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
+
+  copy 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.
+
+    -u        Do not automatically mount received datasets
+
+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
+
+
+#
+# "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 _dummy
+    # full name of the dataset
+    local _ds
+    # dynamic ZFS options
+    local _zfsopts
+
+    _zfsopts=""
+    while getopts "u" _opt ; do
+        case ${_opt} in
+            u)
+                # do not mount newly created datasets
+                _zfsopts="${_zfsopts} -u"
+                ;;
+            \?|:)
+                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
+    _get=$(zfs get -H mountpoint "${_pds}" 2>/dev/null) || { echo "ERROR: dataset \`${_pds}' does not exist" >&2; return 1; }
+    IFS=$'\t' read _dummy _dummy _pmp _dummy <<EOF
+${_get}
+EOF
+    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 get -H mountpoint "${_ds}" >/dev/null 2>/dev/null; then
+        echo "ERROR: dataset \`${_ds}' does already exist" >&2
+        return 1
+    fi
+    zfs create ${_zfsopts} -o atime=off                                                                      "${_ds}"
+    zfs create ${_zfsopts} -o sync=disabled -o setuid=off                                                    "${_ds}/tmp"
+    zfs create ${_zfsopts}                                                                                   "${_ds}/usr"
+    zfs create ${_zfsopts}                                                                                   "${_ds}/var"
+    zfs create ${_zfsopts} -o exec=off -o setuid=off                                                         "${_ds}/var/audit"
+    zfs create ${_zfsopts} -o exec=off -o setuid=off                                                         "${_ds}/var/cache"
+    zfs create ${_zfsopts} -o exec=off -o setuid=off -o compression=off                                      "${_ds}/var/cache/pkg"
+    zfs create ${_zfsopts} -o exec=off -o setuid=off -o compression=off                                      "${_ds}/var/crash"
+    zfs create ${_zfsopts} -o exec=off -o setuid=off                                                         "${_ds}/var/db"
+    zfs create ${_zfsopts} -o exec=on -o setuid=off                                                          "${_ds}/var/db/pkg"
+    zfs create ${_zfsopts} -o readonly=on -o exec=off -o setuid=off                                          "${_ds}/var/empty"
+    zfs create ${_zfsopts} -o exec=off -o setuid=off -o primarycache=metadata                                "${_ds}/var/log"
+    zfs create ${_zfsopts} -o exec=off -o setuid=off -o atime=on                                             "${_ds}/var/mail"
+    zfs create ${_zfsopts} -o sync=disabled -o exec=off -o setuid=off -o compression=off -o primarycache=all "${_ds}/var/run"
+    zfs create ${_zfsopts} -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
+
+    tar -C "${_mp}" --exclude=./var/empty -xJp -f "${_basetxz}" || { echo "ERROR: tar encountered errors" >&2; return 1; }
+}
+
+
+#
+# "copy" -- ZFS copy of datasets
+#
+# command_copy source-dataset destination-dataset
+#
+command_copy() {
+    # source dataset -- destination dataset
+    local _source _dest
+    # dynamic ZFS options
+    local _zfsopts
+
+    _zfsopts=""
+    while getopts "u" _opt ; do
+        case ${_opt} in
+            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 -n -v ${_source} || { echo "ERROR: ZFS operation failed in no-op mode" >&2; return 1; }
+    zfs send -R "${_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
+
+    _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"
+}
+
+
+#
+# Global option handling
+#
+while getopts "Vh" _opt ; do
+    case ${_opt} in
+        V)
+            echo "fjail v${VERSION} (rv:@@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 "$@"
+        ;;
+    privs)
+        command_privs "$@"
+        ;;
+    populate)
+        command_populate "$@"
+        ;;
+    copy)
+        command_copy "$@"
+        ;;
+    *)
+        echo "ERROR: unknown command \`${command}'" >&2
+        exit 2
+        ;;
+esac