#!/bin/sh version=2.0.2 # this will be completed during build. Don't touch it, just execute # make and use the resulting script! JAILDK_COMPLETION=$( cat<<'EOF' # will be modified during installation (jaildk setup) JAILDIR=/jail COMPLETIONCODE EOF ) usage_jaildk() { beg=`tput -T ${TERM:-cons25} md` end=`tput -T ${TERM:-cons25} me` usage=$(cat < ${beg}Building Jails:${end} base -b [-w] - build a new base build -m [-b ] [-v ] - install a build chroot of a jail create - create a new jail from a template clone -s -d [-o ] [-n ] - clone an existing jail or jail version fetchports [-v ] - fetch current port collection ${beg}(Un)installing Jails:${end} install -m [-r function] - install a jail (prepare mounts, devfs etc) uninstall [-w] - uninstall a jail remove - remove a jail or a jail version reinstall [-b ] [-v ] - stop, remove, install and start a jail, if -b and/or -v is set, update the jail config prune [-b | -a | -j - display unused directories ${beg}Maintaining Jails:${end} start - start a jail stop - stop a jail restart - restart a jail status [] [-v] - display status of jails or rc -m [-r ] - execute an rc-script inside a jail ipfw -m - add or remove ipfw rules ${beg}Managing Jails:${end} login [] - login into a jail blogin - chroot into a build jail ${beg}Transferring Jails:${end} freeze [-a -b -v ] - freeze (build an image of) a jail thaw - thaw (install) an image of a jail ${beg}Getting help and internals:${end} completion - print completion code. to use execute in a bash: source <(jaildk completion) help - request help on version - print program version update [-f] - update jaildk from git repository EOF ) echo "$usage" exit 1 } usage_help() { command=$1 usage="usage_${command}" if ! type "$usage" > /dev/null 2>&1; then die "Unknown command $command!" else $usage fi } ex() { echo $rcscript - $* logger -p local0.notice -t jaildk "$rcscript $*" $* } err () { echo "$@" >&2 } bold() { if [ -z "$NO_BOLD" ]; then if [ -z "$BOLD_ON" ]; then BOLD_ON=`tput -T ${TERM:-cons25} md` export BOLD_ON BOLD_OFF=`tput -T ${TERM:-cons25} me` export BOLD_OFF fi echo -n "$BOLD_ON" echo "$@" echo -n "$BOLD_OFF" else echo "$@" fi } fin() { echo "$*" >&2 exit } die() { bold "$*" >&2 exit 1 } load_jail_config() { local jail=$1 if test -d $j/etc/$jail; then # everything inside gets global . $j/etc/$jail/jail.conf else die "Jail $jail is not configured!" fi } die_if_not_exist() { local jail which jailversion jail=$1 which=$2 jailversion=$3 if test -z "$which"; then which="Jail" fi if ! test -d $j/etc/$jail; then die "$which $jail doesn't exist!" fi if test -n "$jailversion"; then if ! test -d $j/etc/$jail/etc-$jailversion; then die "$which $jail $jailversion doesn't exist!" fi fi } parse_jail_conf() { # # just in case we want or have to fetch variables out of # /etc/jail.conf, this is the way to go. Call it like this: # # ip=`parse_jail_conf $jail ip4.addr` # # Output may be empty, so check before using. Multiple variables # of the same type (like multiple ip addresses) will be returned # comma separated. local jail=$1 local search=$2 local JAIL list # fetch 20 lines after "^$jail {", ignore comments egrep -A20 "^$jail" jail.conf | egrep -v "^ *#" | \ # turn each line into an evaluable shell expression \ sed -e 's/ *{//g' -e 's/}//g' -e 's/ *= */=/g' -e 's/;$//' | \ # ignore empty lines \ egrep -v '^$' | while read LINE; do if echo "$LINE" | egrep -q "="; then case $JAIL in $jail) var=`echo "$LINE" | cut -d= -f1` opt=`echo "$LINE" | cut -d= -f2 | sed -e 's/^"//' -e 's/"$//'` case $var in $search) if test -z "$list"; then list="$opt" else list="$list,$opt" fi ;; esac ;; *) echo $list return ;; esac else case $LINE in \*) JAIL=any;; *) JAIL="$LINE";; esac fi done } usage_build() { fin "Usage: $0 build [-m ] [-b ] [-v ] Mount to $j/build read-writable for maintenance. Options: -b Use specified . default: use configured base. -v Mount of . -m One of start, stop or status. default: start." } jaildk_build() { local jail mode BASE VERSION base version jail=$1 mode=start shift BASE='' VERSION='' OPTIND=1; while getopts "b:v:m:" arg; do case $arg in b) BASE=${OPTARG};; v) VERSION=${OPTARG};; m) mode=${OPTARG};; *) usage_build;; esac done if test -z "$jail" -o "$jail" = "-h"; then usage_build fi die_if_not_exist $jail $VERSION load_jail_config $jail if test -n "$VERSION"; then # overridden with -v version=$VERSION fi if test -n "$BASE"; then # dito base=$BASE else if test -n "$buildbase"; then base="$buildbase" elif test -z "$base"; then # nothing configured, use default: latest base=`ls $j/base | tail -1` fi fi # install the jail to build/ jaildk_install $jail -m $mode -r all -w -b $base -v $version case $mode in start) # make it usable ex chroot $j/build/$jail /etc/rc.d/ldconfig onestart ex chroot $j/build/$jail pkg-static bootstrap -f ex mkdir -p $j/build/$jail/usr/local/db ;; esac } pf_ruleset() { # internal helper to [un]install a pf ruleset local conf mode anchor jail conf=$1 mode=$2 anchor=$3 jail=$4 case $mode in start) bold "Installing PF rules for jail $jail:" pfctl -a /jail/$anchor -f $conf -v ;; status) bold "PF NAT rules for jail $jail:" pfctl -a /jail/$anchor -s nat -v echo bold "PF rules for jail $jail:" pfctl -a /jail/$anchor -s rules -v ;; stop) bold "Removing PF rules for jail $jail:" pfctl -a /jail/$anchor -v -F all ;; restart) pf_ruleset $conf stop $anchor $jail pf_ruleset $conf start $anchor $jail ;; esac } pf_map() { local extif proto eip eport mport ip v6 extif=$1 proto=$2 eip=$3 eport=$4 mport=$5 ip=$6 from=$7 v6=${8:-inet} echo "rdr pass on $extif $v6 proto ${proto} from ${from} to ${eip} port ${eport} -> ${ip} port ${mport}" } pf_rule() { local extif proto eip eport v6 extif=$1 proto=$2 eip=$3 eport=$4 v6=$5 echo "pass in quick on $extif $v6 proto ${proto} from any to ${eip} port ${eport}" } pf_nat() { local extif srcip dstip v6 extif=$1 srcip=$2 dstip=$3 v6=$4 echo "nat on $extif $v6 from $srcip to any -> $dstip" } rc_pf() { local jail mode conf ruleset extif ipv4 anchor proto eport mport eports eip allowfrom port jail=$1 mode=$2 conf=$j/etc/$jail/pf.conf ruleset=$j/etc/$jail/pf-ruleset.conf load_jail_config $jail # TODO: # - put this into a separate function # - clean up if generation of pf-ruleset.conf fails somehow # - make a syntax check of the generated rules, if possible case $mode in start|restart) if test -n "$masq_ip" -o -n "$rules" -o -n "$maps"; then # generate a pf.conf based on config variables echo "# generated pf ruleset for jail, generated on ` date`" > $ruleset extif=$(netstat -rnfinet | grep default | cut -f4 -w) # we need to make sure the ip address doesn't contain a mask which # is not required for these rules ipv4=$(dirname $ip) ipv6=$(dirname $ip6) if test -n "$ipv4" -a -n "$maps"; then # nat and rdr come first # SAMPLE ruleset # maps="web ntp kjk" # map_web_proto="tcp" # map_web_exposed_port=80 # map_web_mapped_port=8080 # map_web_exposed_ip="123.12.12.3" # map_web_allow_from="any" # | ip | ip list | table # map_ntp_proto="udp" # map_ntp_exposed_port=123 # map_ntp_mapped_port=1234 # map_ntp_exposed_ip="123.12.12.33" # map_kjk_proto="tcp" # map_kjk_exposed_port="1501 1502 1502}" # maped 1:1 # map_kjk_exposed_ip="123.12.12.33" for map in $maps; do # slurp in the values for this map eval proto=\${map_${map}_proto:-tcp} eval eport=\${map_${map}_exposed_port} eval mport=\${map_${map}_mapped_port:-"${eport}"} eval eip=\${map_${map}_exposed_ip:-$extif} eval allowfrom=\${map_${map}_allow_from:-any} # == from any|ips if test -z "${eport}" -o -z "${eip}"; then echo "Warning: ignoring incomplete map: $map!" continue fi if test -n "${eport}"; then echo "# from map $map" >> $ruleset for port in $eport; do if echo "${eport}" | grep -q " "; then # multiple eports, map 1:1 mport=${port} elif test -z "${mport}"; then mport=${port} fi pf_map "$extif" "${proto}" "${eip}" "${port}" "${mport}" "${ipv4}" "${allowfrom}" >> $ruleset done fi done fi # masq_ip="123.12.12.33" if test -n "$ipv4" -a -n "${masq_ip}"; then pf_nat $extif $ipv4 ${masq_ip} >> $ruleset fi if test -n "$ip6" -a -n "$rules"; then # only required for ipv6, ipv4 is already opened with exposed ports # rules="open web" # rule_open="any" # rule_web_proto="tcp" # rule_web_port="80,443" for rule in $rules; do eval proto=\${rule_${rule}_proto:-tcp} eval eport=\${rule_${rule}_port} if test -n "${eport}"; then echo "# from rule $rule" >> $ruleset pf_rule $extif ${proto} ${ipv6} ${eport} inet6 >> $ruleset else echo "Warning: incomplete rule: $rule!" continue fi done fi fi ;; esac if test -s $ruleset; then anchor="${jail}-jaildk" pf_ruleset $ruleset $mode $anchor $jail fi if test -s $conf; then anchor="${jail}-custom" pf_ruleset $conf $mode $anchor $jail fi } rc_mtree() { local jail mode base version rw conf jail=$1 mode=$2 base=$3 version=$4 rw=$5 rcscript=mtree conf=$j/etc/$jail/$rcscript.conf if test -s $conf; then case $mode in start|restart) if test -n "$rw"; then run=$j/build/$jail/ else run=$j/run/$jail/ fi # needs to run inside jail echo "cat $j/etc/$jail/mtree.conf | chroot $run mtree -p / -Ue | grep -v extra:" cat $j/etc/$jail/mtree.conf | chroot $run mtree -p / -Ue | grep -v "extra:" ;; esac fi } rc_rcoff() { # avoid starting services inside the build chroot # + rc_rcoff db start 12.1-RELEASE-p10 20201026 local jail mode base VERSION BASE rw jail=$1 mode=$2 BASE=$3 VERSION=$4 rw=$5 rcscript=rcoff if test -n "$rw"; then # not required in run mode case $mode in start) if mount | egrep -q "rcoff.*build/$jail"; then bold "union mount $j/build/jail/etc already mounted" else if ! test -d $j/etc/rcoff; then # in order to be backwards compatible to older jaildk # create the rcoff directory on the fly mkdir -p $j/etc/rcoff ( echo "#!/bin/sh" echo 'echo "$0 disabled in build chroot!"' ) > $j/etc/rcoff/rc fi ex mount -t unionfs $j/etc/rcoff $j/build/$jail/etc fi ;; stop) # might fail if executed on a yet not union'ed etc if mount | egrep -q "rcoff.*build/$jail"; then ex umount $j/build/$jail/etc fi ;; esac fi } rc_ports() { local jail mode BASE VERSION rw jail=$1 mode=$2 BASE=$3 VERSION=$4 rw=$5 rcscript=ports load_jail_config $jail if test -z "$ports"; then # ports not configured, abort return fi if ! test -d "$j/ports/$VERSION"; then die "Ports tree $j/ports/$VERSION doesn't exist yet. Consider creating it with 'jaildk fetchports [-v ]'" fi if test -n "$buildbase" -a -n "$rw"; then # we only support ports if a buildbase is configured case $mode in start) if mount -v | grep -q " $j/build/$jail/usr/ports "; then bold "$j/build/$jail/usr/ports already mounted!" else ex mount -t nullfs -o rw $j/ports/$version $j/build/$jail/usr/ports fi ;; stop) if mount -v | grep -q " $j/build/$jail/usr/ports "; then ex umount $j/build/$jail/usr/ports else bold "$j/build/$jail/usr/ports not mounted!" fi ;; esac fi } rc_mount() { local jail mode BASE VERSION rw conf run base version \ src dest fs opts size perm source jail=$1 mode=$2 BASE=$3 VERSION=$4 rw=$5 rcscript=mount load_jail_config $jail conf=$j/etc/$jail/$rcscript.conf if ! test -e "$conf"; then return fi if test -n "$rw"; then run=$j/build if test -n "$BASE"; then base=$BASE fi if test -n "$VERSION"; then version=$VERSION fi else run=$j/run fi die_if_not_exist $jail # parse the config and (u)mount case $mode in stop) tail -r $conf | grep -v "#" ;; *) grep -v "#" $conf ;; esac | while read LINE; do # This command expands variables and performs field-splitting: set -- $(eval echo \""$LINE"\") # Skip empty lines: case "$1" in "") continue ;; esac src=$1 dest=$2 fs=$3 opts=$4 size=$5 perm=$6 if test -n "$rw"; then if ! echo $src | grep -q base/; then opts=`echo "$opts" | sed 's/ro/rw/g'` fi fi case $mode in start) if mount -v | grep " $run/$dest " > /dev/null ; then bold "$run/$dest already mounted!" else case $fs in mfs) ex mdmfs -o $opts -s $size -p $perm md $run/$dest ;; nullfs|unionfs) source=$j/$src if echo $src | egrep -q "^/"; then source=$src fi if ! test -d "$source"; then die "Source dir $source doesn't exist!" fi if ! test -d "$run/$dest"; then die "Dest dir $run/$dest doesn't exist!" fi ex mount -t $fs -o $opts $source $run/$dest ;; devfs) ex mount -t devfs dev $run/$dest ;; *) bold "unknown filesystem type $fs!" ;; esac fi ;; stop) if mount -v | grep " $run/$dest " > /dev/null ; then ex umount $run/$dest if mount -v | grep " $run/$dest " > /dev/null ; then # still mounted! forcing ex umount -f $run/$dest fi else bold "$run/$dest not mounted!" fi ;; status) if mount -v | grep " $run/$dest " > /dev/null ; then echo "$run/$dest mounted" else bold "$run/$dest not mounted" fi ;; *) bold "Usage: $0 install mount {start|stop|status|restart}" ;; esac done } usage_install() { fin "Usage: $0 install [-m ] [-r rc-function] Install according to its config. Options: -m Mode can either be start, stop or status. default: start -r Only execute function with parameter. default: all. Available rc.d-scripts: $RCSCRIPTS_START" } jaildk_install() { local jail mode rcd rw base version rcscripts type jail=$1 mode=start shift rcd='' # options -b -w -v are undocumented, used by jaildk_build() only rw='' base='' version='' OPTIND=1; while getopts "r:b:v:wm:" arg; do case $arg in w) rw=1;; b) base=${OPTARG};; v) version=${OPTARG};; r) rcd=${OPTARG};; m) mode=${OPTARG};; *) usage_install;; esac done if test -z "$jail" -o "$jail" = "-h"; then usage_install fi if test -z "$rcd"; then # default just install everything rcd=all fi case $mode in start|stop|restart|status) :;; *) usage_install;; esac die_if_not_exist $jail if test "$rcd" = "all"; then if test -n "$rw"; then case $mode in start) rcscripts="$RW_RCSCRIPTS_START";; stop) rcscripts="$RW_RCSCRIPTS_STOP";; esac else case $mode in start) rcscripts="$RCSCRIPTS_START";; stop) rcscripts="$RCSCRIPTS_STOP";; esac fi else rcscripts="rc_${rcd}" if ! type "$rcscripts" > /dev/null 2>&1; then die "rc function $rcd doesn't exist!" fi fi type="jail" if test -n "$rw"; then type="build chroot" fi case $mode in start) bold "Installing $type $jail" ;; stop) bold "Unstalling $type $jail" ;; esac for rcscript in $rcscripts; do $rcscript $jail $mode $base $version $rw || exit 1 done } usage_uninstall() { fin "Usage: $0 uninstall [-w] Uninstall . Options: -w Uninstall writable build chroot. -a Uninstall jail and build chroot." } jaildk_uninstall() { # wrapper around _install local jail mode base version all rw jail=$1 shift rw='' all='' base='' version='' OPTIND=1; while getopts "wa" arg; do case $arg in w) rw="-w";; a) all=1; rw="-w";; *) usage_uninstall;; esac done if test -z "$jail" -o "$jail" = "-h"; then usage_uninstall fi die_if_not_exist $jail if jls | egrep -q "${jail}"; then die "Jail $jail($version) is still running, stop it before removing!" fi if test -n "$rw"; then # we need to find out base and version of actually # mounted jail, but cannot just use the jail config # since the user might have mounted another version base=$(mount | egrep "/base/.*/$jail " | cut -d' ' -f1 | sed 's|.*/||') version=$(mount | egrep "/appl/.*/$jail/" | cut -d' ' -f1 | sed 's/.*\-//') fi if test -z "$base"; then # no base no umount! rw='' all='' fi if test -n "$all"; then jaildk_install $jail -m stop -r all jaildk_install $jail -m stop -r all -b $base -v $version -w else jaildk_install $jail -m stop -r all -b $base -v $version $rw fi } usage_base() { fin "Usage: $0 base [-f] -b [-w] Build a base directory from bsd install media. Options: -b can be the name of a base (e.g. 12.2-RELEASE) or a directory where it shall be created -w Create a writable base, including compiler and other build stuff. Use this if you want to use the ports collection. -f force mode, remove any old dist files. -s