#!/bin/sh # Copyright 2011 CyberLeo, All Rights Reserved # http://wiki.cyberleo.net/wiki/CyberLeo/COPYRIGHT : ${ORIG_USER=$(id -un)} # Need root beyond here [ "$(id -u)" -eq 0 ] || exec sudo env "J_ARCH=${J_ARCH}" "J_BASE=${J_BASE}" "J_NAME=${J_NAME}" "J_USER=${J_USER:-${USER}}" "ORIG_USER=${ORIG_USER}" "DISPLAY=${DISPLAY}" "${0}" "${@}" meh() { printf " \033[1;32m*\033[0m %s%s\n" "${jname:+${jname}: }" "${*}"; } omg() { printf " \033[1;33m*\033[0m %s%s\n" "${jname:+${jname}: }" "${*}"; } wtf() { printf " \033[1;31m*\033[0m %s%s\n" "${jname:+${jname}: }" "${*}"; exit 1; } pebkac() { [ "${*}" ] && printf "%s\n\n" "${*}" cat < [arguments] list ls list available chroots status show jail status start prepare an existing chroot for use stop undo what 'start' did kill Forcefully kill all processes; only applicable with cgroups enter shell spawn a shell or command within the chroot eval evaluate a shell command line within the chroot EOF exit 1 } cmd="$(basename "${0}")" jarch="${J_ARCH:-$(uname -m)}" jbase="${J_BASE:-$(realpath "$(dirname "${0}")/../")}" jname="${J_NAME:-$(basename "${1}")}" #" juser="${J_USER}" # cgroup to use; if not found, cgroup support will not be used jcgroup="freezer" # cgroup parent to use (${cgroup}/${parent}/${jname}) jcgparent="j" # Remove chroot name from argument stack, if passed in [ "${J_NAME}" ] || shift # Propagate certain environment variables; sterilize the rest of the environment jenv=" LANG=${LANG} TERM=${TERM} USER=${USER} " # Debian-specific init: prepare Debian chroot with debootstrap j_init_debian() { jdir="${1}" suite="$(echo "${2:-squeeze}" | tr 'A-Z' 'a-z')" # Validation [ "$(which debootstrap 2>&-)" ] || pebkac "j_init_debian: debootstrap not found" [ "${jdir}" ] || pebkac "j_init_debian: jdir must be specified" [ ! -d "${jdir}" ] || pebkac "j_init_debian: jdir must not exist ('${jdir}')" [ -d "$(dirname "${jdir}")" ] || pebkac "j_init_debian: parent of jdir must exist ('${jdir}')" # Figure out arch case "${jarch}" in x86|i386) arch=i386 ;; amd64|x86_64|x64) arch=amd64 ;; *) pebkac "Unsupported arch '${jarch}'" ;; esac cmd="debootstrap --arch=${arch} --include=curl,file,less,locales,sudo,build-essential,libreadline-dev,zlib1g-dev '${suite}' '${jdir}'" echo "Executing ${cmd}" eval "${cmd}" # Make sure locales are generated on first start mkdir -p "${jdir}/etc/rcJ.d" cat > "${jdir}/etc/rcJ.d/S00localegen" <<"EOF" #!/bin/sh /bin/sed -i '/en_US/s/^# //' /etc/locale.gen /usr/sbin/locale-gen /bin/rm -f "${0}" EOF chmod 755 "${jdir}/etc/rcJ.d/S00localegen" } # Gentoo-specific init: prepare Gentoo chroot with stage3+portage tarballs j_init_gentoo() { arch="$(uname -m)" base="http://distfiles.gentoo.org/releases/${arch}/autobuilds" pointer="${base}/latest-stage3.txt" # Fetch stage3 # Fetch portage # Validate signatures # Unpack stage3 # Unpack portage } # Create a new chroot, somehow j_init() { # Make sure this does NOT exist jname="${1:-jname}" dist="$(echo "${2:-debian}" | tr 'A-Z' 'a-z')" jdir="${jbase}/${jname}" [ -d "${jdir}" ] && pebkac "j_init: ${jname} already exists" shift 2 case "${dist}" in debian) j_init_debian "${jdir}" "${@}" ;; gentoo) j_init_gentoo "${jdir}" "${@}" ;; *) pebkac "Unsupported distro '${dist}'" ;; esac } # Figure out and set chroot parameters; needed for all functions that follow j_params() { ( jname="${1:-jname}" # Make sure jname is not empty if [ -z "${jname}" ] then printf "jerror='%s'\n" "jname empty" return 1 fi # Given a chroot name, find and set up the chroot dir jdir="${jbase}/${jname}" jroot="${jdir}/root" if [ ! -d "${jdir}" -o ! -d "${jroot}" ] then printf "jerror='%s'\n" "not a directory" return 1 fi # Where is the shell? jshell="" for shell in /bin/bash /usr/bin/bash /usr/local/bin/bash /bin/sh do if [ -f "${jroot}/${shell}" ] then jshell=${shell} break fi done if [ -z "${jshell}" ] then printf "jerror='%s'\n" "unable to locate usable shell; is this a jail?" return 1 fi printf "jerror='' jname='%s' jdir='%s' jroot='%s' jshell='%s'\n" "${jname}" "${jdir}" "${jroot}" "${jshell}" ) } # Find the 'freezer' cgroup, to determine whether the chroot can be further isolated j_locate_cgroup_mount() { cgroup="${1:-freezer}" cat /proc/mounts | while read src dir type opts fsck dump extra do [ "${type}" = "cgroup" ] || continue oldifs="${IFS}" IFS=',' set -- ${opts} IFS=${oldifs} while [ "${1}" ] do [ "${1}" = "${cgroup}" ] && echo "${dir}" && break 2 shift done done } # Cache and expose cgroup mountpoint for given chroot; make sure it exists j_cgroup() { [ "${jcg}" ] && { echo "${jcg}"; return; } jcg="$(j_locate_cgroup_mount "${cgroup}")/${jcgparent}/${jname}" mkdir -p "${jcg}" echo "${jcg}" } j_cg_trap() { [ "$(j_cgroup)" ] || return while [ "${1}" ] do echo "${1}" > "$(j_cgroup)/tasks" shift done } # Run a command within a cgroup, if available j_cg_eval() { j_cg="$(j_cgroup)" if [ "$(j_cgroup)" ] then sh -ex < "$(j_cgroup)/tasks" eval "${*}" EOF else eval "${*}" fi } # Copy ipcc into the jail and add helpful symlinks, so it can communicate # simple commands to the outside j_ipc_setup() { ipcc="$(dirname "${0}")/ipcc" cp -f "${ipcc}" "${jroot}/bin" ( cd "${jroot}/bin" [ -e ee ] || ln -s ipcc ee [ -e ff ] || ln -s ipcc ff [ -e gitk ] || ln -s ipcc gitk [ -e gitka ] || ln -s ipcc gitka ) } # Launch ipcd on jail j_ipc_start() { ipcd_pid="${jdir}/ipcd.pid" # Kill any running IPCd; remove stale pid file and socket if [ -f "${ipcd_pid}" ] then pid="$(cat "${ipcd_pid}")" if [ "${pid}" ] then if ps ouser=,command= "${pid}" | grep "^${ORIG_USER}" | grep -q "ipcd" then omg "IPCd: already running! Killing" kill -TERM "${pid}" fi omg "IPCd: removing stale pid file" rm -f "${ipcd_pid}" "${jroot}/tmp/jipc" fi fi su "${ORIG_USER}" "${jbase}/j/ipcd" "${jdir}" & pid="${!}" echo "${pid}" > "${ipcd_pid}" j_cg_trap "${pid}" } # Stop ipcd on jail j_ipc_stop() { ipcd_pid="${jdir}/ipcd.pid" [ -f "${ipcd_pid}" ] && pid="$(cat "${ipcd_pid}")" [ "${pid}" ] && kill -TERM ${pid} rm -f "${ipcd_pid}" } # Is this a chroot? j_is() { eval $(j_params "${1}") [ "${jerror}" ] && return 1 || return 0 } # List available chroots j_ls() { ( cd "${jbase}"; ls -1 ) | while read jname do j_is "${jname}" && echo "${jname}" done } # Chroot is 'up' if /dev/pts and /proc are mounted j_up() { jname="${1:-${jname}}" eval "$(j_params "${jname}")" [ "${jerror}" ] && wtf "${jerror}" grep -q "^devpts ${jroot}/dev/pts devpts" /proc/mounts || return 1 grep -q "^proc ${jroot}/proc proc" /proc/mounts || return 1 return 0 } # Poll chroot status (j_up) j_status() { [ -z "${1}" ] && set - $(j_ls) while [ "${1}" ] do j_up "${1}" && meh "$(printf '\033[1;32mup\033[0m')" || meh "$(printf '\033[1;31mdown\033[0m')" shift done } # Mount /dev/pts and /proc in the chroot j_start() { jname="${1:-${jname}}" j_up "${jname}" && return 0 eval "$(j_params "${jname}")" meh "starting ${jname} ..." mount -t devpts devpts "${jroot}/dev/pts" mount -t proc proc "${jroot}/proc" # Copy in ipcc and launch ipcd j_ipc_setup j_ipc_start # Start all services in /etc/rcJ.d j_root_eval "${jname}" '[ -d /etc/rcJ.d ] && ( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort | sed -e "s/$/ start/" | sh )' } # Execute command in chroot as root j_root_eval() { jname="${1:-${jname}}"; shift j_up "${jname}" || wtf "chroot not running" eval "$(j_params "${jname}")" j_cg_trap $$ env -i ${jenv} /usr/bin/chroot "${jroot}" /bin/sh -c "${*}" } # Execute command in chroot j_eval() { jname="${1:-${jname}}"; shift j_up "${jname}" || wtf "chroot not running" eval "$(j_params "${jname}")" j_cg_trap $$ env -i ${jenv} /usr/bin/chroot "${jroot}" /bin/su "${juser:-${USER}}" -c "${*}" } j_shell() { jname="${1:-${jname}}" eval "$(j_params "${jname}")" j_eval "${jname}" "cd; exec ${jshell} -l" } # Unmount /dev/pts and /proc in the chroot j_stop() { jname="${1:-${jname}}" eval "$(j_params "${jname}")" j_up "${jname}" || return 0 meh "stopping ${jname} ..." # Stop all services in /etc/rcJ.d j_root_eval "${jname}" '[ -d /etc/rcJ.d ] && ( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort -r | sed -e "s/$/ stop/" | sh )' # Terminate ipcd j_ipc_stop umount "${jroot}/proc" umount "${jroot}/dev/pts" } # Send a signal to all processes inside a chroot, if possible j_signal() { sig="${1:-HUP}" jname="${2:-${jname}}" [ "$(j_cgroup)" ] || { echo "No cgroup support; cannot signal"; return 1; } pids="$(cat "$(j_cgroup)/tasks")" if [ "${pids}" ] then echo Sending ${sig} to ${pids} ... echo "FROZEN" > "$(j_cgroup)/freezer.state" kill -${sig} ${pids} echo "THAWED" > "$(j_cgroup)/freezer.state" fi } j_kill() { jname="${1:-${jname}}" eval "$(j_params "${jname}")" meh "killing ${jname} ..." j_signal "TERM" "${jname}" sleep 2 j_signal "KILL" "${jname}" } case "${cmd}" in init|create) j_init "${jname}" "${@}" ;; ls|list) j_ls ;; status) j_status "${jname}" "${@}" ;; start) j_start "${jname}" ;; shell|enter) j_shell "${jname}" ;; eval) j_eval "${jname}" "${*}" ;; stop) j_stop "${jname}" ;; kill) j_kill "${jname}" ;; *) pebkac ;; esac