view bin/fjail @ 93:c95726f83f84

Style: :Id: -> :ID:
author Franz Glasner <fzglas.hg@dom66.de>
date Sat, 07 Sep 2019 11:48:29 +0200
parents a8e76893140f
children 6f6058939e48
line wrap: on
line source

#!/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:   3-Clause BSD 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
"


# 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