]> CyberLeo.Net >> Repos - CDN/j.git/blob - j
j: avoid non-zero exit code on chroot startup if /etc/rcJ.d does not exist
[CDN/j.git] / j
1 #!/bin/sh
2 # Copyright 2011 CyberLeo, All Rights Reserved
3 # http://wiki.cyberleo.net/wiki/CyberLeo/COPYRIGHT
4
5 : ${ORIG_USER=$(id -un)}
6
7 # Need root beyond here
8 [ "$(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}" "${@}"
9
10 meh() { printf " \033[1;32m*\033[0m %s%s\n" "${jname:+${jname}: }" "${*}"; }
11 omg() { printf " \033[1;33m*\033[0m %s%s\n" "${jname:+${jname}: }" "${*}"; }
12 wtf() { printf " \033[1;31m*\033[0m %s%s\n" "${jname:+${jname}: }" "${*}"; exit 1; }
13 pebkac() {
14   [ "${*}" ] && printf "%s\n\n" "${*}"
15   cat <<EOF
16 Usage:
17   <command> <name> [arguments]
18   list
19   ls      list available chroots
20
21   status  show jail status
22
23   start   prepare an existing chroot for use
24
25   stop    undo what 'start' did
26
27   kill    Forcefully kill all processes; only applicable with cgroups
28
29   enter
30   shell   spawn a shell or command within the chroot
31
32   eval    evaluate a shell command line within the chroot
33
34   mount   mount chroot filesystems according to chroot fstab
35
36   umount  umount chroot filesystems that were mounted with 'mount'
37
38 EOF
39   exit 1
40 }
41
42 cmd="$(basename "${0}")"
43 jarch="${J_ARCH:-$(uname -m)}"
44 jbase="${J_BASE:-$(realpath "$(dirname "${0}")/../")}"
45 jname="${J_NAME:-$(basename "${1}")}" #"
46 juser="${J_USER}"
47
48 # cgroup to use; if not found, cgroup support will not be used
49 jcgroup="freezer"
50 # cgroup parent to use (${cgroup}/${parent}/${jname})
51 jcgparent="j"
52
53 # Remove chroot name from argument stack, if passed in
54 [ "${J_NAME}" ] || shift
55
56 # Propagate certain environment variables; sterilize the rest of the environment
57 jenv="
58   LANG=${LANG}
59   TERM=${TERM}
60   USER=${USER}
61 "
62
63 # Debian-specific init: prepare Debian chroot with debootstrap
64 j_init_debian() {
65   jdir="${1}"
66   suite="$(echo "${2:-squeeze}" | tr 'A-Z' 'a-z')"
67
68   # Validation
69   [ "$(which debootstrap 2>&-)" ] || pebkac "j_init_debian: debootstrap not found"
70   [ "${jdir}" ] || pebkac "j_init_debian: jdir must be specified"
71   [ ! -d "${jdir}" ] || pebkac "j_init_debian: jdir must not exist ('${jdir}')"
72   [ -d "$(dirname "${jdir}")" ] || pebkac "j_init_debian: parent of jdir must exist ('${jdir}')"
73
74   # Figure out arch
75   case "${jarch}" in
76   x86|i386) arch=i386 ;;
77   amd64|x86_64|x64) arch=amd64 ;;
78   *) pebkac "Unsupported arch '${jarch}'" ;;
79   esac
80
81   cmd="debootstrap --arch=${arch} --include=curl,file,less,locales,sudo,build-essential,libreadline-dev,zlib1g-dev '${suite}' '${jdir}'"
82   echo "Executing ${cmd}"
83   eval "${cmd}"
84
85   # Make sure locales are generated on first start
86   mkdir -p "${jdir}/etc/rcJ.d"
87   cat > "${jdir}/etc/rcJ.d/S00localegen" <<"EOF"
88 #!/bin/sh
89 /bin/sed -i '/en_US/s/^# //' /etc/locale.gen
90 /usr/sbin/locale-gen
91 /bin/rm -f "${0}"
92 EOF
93   chmod 755 "${jdir}/etc/rcJ.d/S00localegen"
94 }
95
96 # Gentoo-specific init: prepare Gentoo chroot with stage3+portage tarballs
97 j_init_gentoo() {
98   arch="$(uname -m)"
99   base="http://distfiles.gentoo.org/releases/${arch}/autobuilds"
100   pointer="${base}/latest-stage3.txt"
101   # Fetch stage3
102   # Fetch portage
103   # Validate signatures
104   # Unpack stage3
105   # Unpack portage
106 }
107
108 # Create a new chroot, somehow
109 j_init() {
110   # Make sure this does NOT exist
111   jname="${1:-jname}"
112   dist="$(echo "${2:-debian}" | tr 'A-Z' 'a-z')"
113   jdir="${jbase}/${jname}"
114   [ -d "${jdir}" ] && pebkac "j_init: ${jname} already exists"
115   shift 2
116   case "${dist}" in
117   debian) j_init_debian "${jdir}" "${@}" ;;
118   gentoo) j_init_gentoo "${jdir}" "${@}" ;;
119   *) pebkac "Unsupported distro '${dist}'" ;;
120   esac
121 }
122
123 # Figure out and set chroot parameters; needed for all functions that follow
124 j_params() {
125   ( jname="${1:-jname}"
126
127     # Make sure jname is not empty
128     if [ -z "${jname}" ]
129     then
130       printf "jerror='%s'\n" "jname empty"
131       return 1
132     fi
133
134     # Given a chroot name, find and set up the chroot dir
135     jdir="${jbase}/${jname}"
136     jroot="${jdir}/root"
137     if [ ! -d "${jdir}" -o ! -d "${jroot}" ]
138     then
139       printf "jerror='%s'\n" "not a directory"
140       return 1
141     fi
142
143     # Does this have a valid fstab file?
144     [ -f "${jdir}/fstab" ] && jfstab="${jdir}/fstab"
145
146     # Where is the shell?
147     jshell=""
148     for shell in /bin/bash /usr/bin/bash /usr/local/bin/bash /bin/sh
149     do
150       if [ -f "${jroot}/${shell}" ]
151       then
152         jshell=${shell}
153         break
154       fi
155     done
156     if [ -z "${jshell}" ]
157     then
158       printf "jerror='%s'\n" "unable to locate usable shell; is this a jail?"
159       return 1
160     fi
161
162     printf "jerror='' jname='%s' jdir='%s' jroot='%s' jfstab='%s' jshell='%s'\n" "${jname}" "${jdir}" "${jroot}" "${jfstab}" "${jshell}"
163   )
164 }
165
166 # Find the 'freezer' cgroup, to determine whether the chroot can be further isolated
167 j_locate_cgroup_mount() {
168   cgroup="${1:-freezer}"
169   cat /proc/mounts | while read src dir type opts fsck dump extra
170   do
171     [ "${type}" = "cgroup" ] || continue
172     oldifs="${IFS}"
173     IFS=','
174     set -- ${opts}
175     IFS=${oldifs}
176     while [ "${1}" ]
177     do
178       [ "${1}" = "${cgroup}" ] && echo "${dir}" && break 2
179       shift
180     done
181   done
182 }
183
184 # Cache and expose cgroup mountpoint for given chroot; make sure it exists
185 j_cgroup() {
186   [ "${jcg}" ] && { echo "${jcg}"; return; }
187   jcg="$(j_locate_cgroup_mount "${cgroup}")/${jcgparent}/${jname}"
188   mkdir -p "${jcg}"
189   echo "${jcg}"
190 }
191
192 j_cg_trap() {
193   [ "$(j_cgroup)" ] || return
194   while [ "${1}" ]
195   do
196     echo "${1}" > "$(j_cgroup)/tasks"
197     shift
198   done
199 }
200
201 # Run a command within a cgroup, if available
202 j_cg_eval() {
203   j_cg="$(j_cgroup)"
204   if [ "$(j_cgroup)" ]
205   then
206     sh -ex <<EOF
207 echo $$ > "$(j_cgroup)/tasks"
208 eval "${*}"
209 EOF
210   else
211     eval "${*}"
212   fi
213 }
214
215 # Copy ipcc into the jail and add helpful symlinks, so it can communicate
216 # simple commands to the outside
217 j_ipc_setup() {
218   ipcc="$(dirname "${0}")/ipcc"
219   cp -f "${ipcc}" "${jroot}/bin"
220   ( cd "${jroot}/bin"
221     [ -e ee ] || ln -s ipcc ee
222     [ -e ff ] || ln -s ipcc ff
223     [ -e gitk ] || ln -s ipcc gitk
224     [ -e gitka ] || ln -s ipcc gitka
225   )
226 }
227
228 # Launch ipcd on jail
229 j_ipc_start() {
230   ipcd_pid="${jdir}/ipcd.pid"
231   # Kill any running IPCd; remove stale pid file and socket
232   if [ -f "${ipcd_pid}" ]
233   then
234     pid="$(cat "${ipcd_pid}")"
235     if [ "${pid}" ]
236     then
237       if ps ouser=,command= "${pid}" | grep "^${ORIG_USER}" | grep -q "ipcd"
238       then
239         omg "IPCd: already running! Killing"
240         kill -TERM "${pid}"
241       fi
242       omg "IPCd: removing stale pid file"
243       rm -f "${ipcd_pid}" "${jroot}/tmp/jipc"
244     fi
245   fi
246   su "${ORIG_USER}" "${jbase}/j/ipcd" "${jdir}" &
247   pid="${!}"
248   echo "${pid}" > "${ipcd_pid}"
249   j_cg_trap "${pid}"
250 }
251
252 # Stop ipcd on jail
253 j_ipc_stop() {
254   ipcd_pid="${jdir}/ipcd.pid"
255   [ -f "${ipcd_pid}" ] && pid="$(cat "${ipcd_pid}")"
256   [ "${pid}" ] && kill -TERM ${pid}
257   rm -f "${ipcd_pid}"
258 }
259
260 # Is this a chroot?
261 j_is() {
262   eval $(j_params "${1}")
263   [ "${jerror}" ] && return 1 || return 0
264 }
265
266 # List available chroots
267 j_ls() {
268   ( cd "${jbase}"; ls -1 ) | while read jname
269   do
270     j_is "${jname}" && echo "${jname}"
271   done
272 }
273
274 # Chroot is 'up' if /dev/pts and /proc are mounted
275 j_up() {
276   jname="${1:-${jname}}"
277   eval "$(j_params "${jname}")"
278   [ "${jerror}" ] && wtf "${jerror}"
279   grep -q "^devpts ${jroot}/dev/pts devpts" /proc/mounts || return 1
280   grep -q "^proc ${jroot}/proc proc" /proc/mounts || return 1
281   return 0
282 }
283
284 # Poll chroot status (j_up)
285 j_status() {
286   [ -z "${1}" ] && set - $(j_ls)
287   while [ "${1}" ]
288   do
289     j_up "${1}" && meh "$(printf '\033[1;32mup\033[0m')" || meh "$(printf '\033[1;31mdown\033[0m')"
290     shift
291   done
292 }
293
294 # Copy a mount from the host (for things like /dev, /proc, /sys, et alia)
295 j_fstab_copy_mount() {
296   grep -Eq "${1}[[:space:]]" "${tmp_fstab}" || grep -E \
297     "[[:space:]]${1}[[:space:]]" /proc/mounts | head -n 1 >> "${tmp_fstab}"
298 }
299
300 # Prepare fstab for use:
301 # * Strip comments
302 # * Make sure certain required mounts exist (/dev/pts, /proc)
303 # * Sort by mountpoint, to ensure mounts are performed in the correct order
304 j_fstab_synthesize() {
305   local tmp_fstab="$(mktemp "${jdir}/.fstab.XXXXXXXX")"
306   [ -f "${jfstab}" ] && sed -e 's/#.*$//; /^\W*$/d' "${jfstab}" > "${tmp_fstab}"
307   j_fstab_copy_mount "/dev/pts"
308   j_fstab_copy_mount "/proc"
309   sort -k2 "${tmp_fstab}"
310   rm -f "${tmp_fstab}"
311 }
312
313 # Process fstab file and mount the filesystems therein; if no fstab file (or if
314 # excludes certain critical filesystems) then augment it. This will also maintain
315 # an mtab file so they can be unmounted later (and so df works inside chroot).
316 #
317 # All mountpoints listed in this file will be prepended with ${jroot} prior to
318 # their being mounted or umounted! Make sure the fstab mountpoints are relative
319 # to the chroot root!
320 j_fstab_mount() {
321   echo "rootfs / rootfs rw 0 0" > "${jdir}/mtab"
322   j_fstab_synthesize | while read src dir type opts fsck dump
323   do
324     mkdir -p "${dir}" || break
325     mount -t "${type}" -o "${opts}" "${src}" "${jroot}/${dir}" || break
326     printf "%s %s %s %s 0 0\n" "${src}" "/${dir##/}" "${type}" "${opts}" >> "${jdir}/mtab"
327   done
328 }
329
330 # Process fstab and mtab files and unmount the filesystems therein (opposite j_fstab_mount)
331 j_fstab_umount() {
332   [ -s "${jdir}/mtab" ] || j_fstab_synthesize > "${jdir}/mtab"
333   tac "${jdir}/mtab" | while read src dir unused
334   do
335     # Mounted?
336     grep -Eq "[[:space:]]${jroot}${dir%%/}[[:space:]]" /proc/mounts || continue
337     umount -f "${jroot}/${dir}" || umount -l "${jroot}/${dir}" || break
338   done
339   :>"${jdir}/mtab"
340 }
341
342 # Start up chroot
343 j_start() {
344   jname="${1:-${jname}}"
345   j_up "${jname}" && return 0
346   eval "$(j_params "${jname}")"
347   meh "starting ${jname} ..."
348
349   # Mount filesystems and copy in /etc/mtab
350   j_fstab_mount
351   cp "${jdir}/mtab" "${jroot}/etc/mtab"
352
353   # Copy in ipcc and launch ipcd
354   j_ipc_setup
355   j_ipc_start
356
357   # Start all services in /etc/rcJ.d
358   [ -d "${jroot}/etc/rcJ.d" ] || return 0
359   j_root_eval "${jname}" '( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort | sed -e "s/$/ start/" | sh )'
360 }
361
362 # Execute command in chroot as root
363 j_root_eval() {
364   jname="${1:-${jname}}"; shift
365   j_up "${jname}" || wtf "chroot not running"
366   eval "$(j_params "${jname}")"
367   j_cg_trap $$
368   env -i ${jenv} /usr/bin/chroot "${jroot}" /bin/sh -c "${*}"
369 }
370
371 # Execute command in chroot
372 j_eval() {
373   jname="${1:-${jname}}"; shift
374   j_up "${jname}" || wtf "chroot not running"
375   eval "$(j_params "${jname}")"
376   j_cg_trap $$
377   env -i ${jenv} /usr/bin/chroot "${jroot}" /bin/su "${juser:-${USER}}" -c "${*}"
378 }
379
380 # Invoke a shell within the chroot
381 j_shell() {
382   jname="${1:-${jname}}"
383   eval "$(j_params "${jname}")"
384   j_eval "${jname}" "cd; exec ${jshell} -l"
385 }
386
387 # Shut down chroot
388 j_stop() {
389   jname="${1:-${jname}}"
390   eval "$(j_params "${jname}")"
391   j_up "${jname}" || return 0
392   meh "stopping ${jname} ..."
393
394   # Stop all services in /etc/rcJ.d
395   j_root_eval "${jname}" '[ -d /etc/rcJ.d ] && ( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort -r | sed -e "s/$/ stop/" | sh )'
396
397   # Terminate ipcd
398   j_ipc_stop
399
400   # Unmount filesystems
401   j_fstab_umount
402 }
403
404 # Send a signal to all processes inside a chroot, if possible
405 j_signal() {
406   sig="${1:-HUP}"
407   jname="${2:-${jname}}"
408
409   [ "$(j_cgroup)" ] || { echo "No cgroup support; cannot signal"; return 1; }
410   pids="$(cat "$(j_cgroup)/tasks")"
411   if [ "${pids}" ]
412   then
413     echo Sending ${sig} to ${pids} ...
414     echo "FROZEN" > "$(j_cgroup)/freezer.state"
415     kill -${sig} ${pids}
416     echo "THAWED" > "$(j_cgroup)/freezer.state"
417   fi
418 }
419
420 # Kill all processes inside chroot (TERM, then KILL)
421 j_kill() {
422   jname="${1:-${jname}}"
423   eval "$(j_params "${jname}")"
424   meh "killing ${jname} ..."
425
426   j_signal "TERM" "${jname}"
427   sleep 2
428   j_signal "KILL" "${jname}"
429 }
430
431 # Hook to allow mounting chroot mounts from command line
432 j_mount() {
433   jname="${1:-${jname}}"
434   eval "$(j_params "${jname}")"
435   j_fstab_mount
436 }
437
438 # Hook to allow unmounting chroot mounts from command line
439 j_umount() {
440   jname="${1:-${jname}}"
441   eval "$(j_params "${jname}")"
442   j_fstab_umount
443 }
444
445 case "${cmd}" in
446 init|create) j_init "${jname}" "${@}" ;;
447 ls|list) j_ls ;;
448 status) j_status "${jname}" "${@}" ;;
449 start) j_start "${jname}" ;;
450 shell|enter) j_shell "${jname}" ;;
451 eval) j_eval "${jname}" "${*}" ;;
452 stop) j_stop "${jname}" ;;
453 kill) j_kill "${jname}" ;;
454 mount) j_mount "${jname}" ;;
455 umount) j_umount "${jname}" ;;
456 *) pebkac ;;
457 esac