]> CyberLeo.Net >> Repos - CDN/j.git/blob - j
j: attempt to resolve the username to uid before entering chroot; use that as a param...
[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   j_root_eval "${jname}" '[ -d /etc/rcJ.d ] && ( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort | sed -e "s/$/ start/" | sh )'
359 }
360
361 # Execute command in chroot as root
362 j_root_eval() {
363   jname="${1:-${jname}}"; shift
364   j_up "${jname}" || wtf "chroot not running"
365   eval "$(j_params "${jname}")"
366   j_cg_trap $$
367   env -i ${jenv} /usr/bin/chroot "${jroot}" /bin/sh -c "${*}"
368 }
369
370 # Execute command in chroot
371 j_eval() {
372   jname="${1:-${jname}}"; shift
373   j_up "${jname}" || wtf "chroot not running"
374   eval "$(j_params "${jname}")"
375   j_cg_trap $$
376   # Attempt to glean user ID stuff
377   uid="$(j_root_eval "${jname}" "/usr/bin/id -u '${juser:-${USER}}' 2>&-")"
378   env -i ${jenv} /usr/bin/chroot --userspec="${uid:-0}" "${jroot}" /bin/sh -c "${*}"
379 }
380
381 # Invoke a shell within the chroot
382 j_shell() {
383   jname="${1:-${jname}}"
384   eval "$(j_params "${jname}")"
385   j_eval "${jname}" "cd; exec ${jshell} -l"
386 }
387
388 # Shut down chroot
389 j_stop() {
390   jname="${1:-${jname}}"
391   eval "$(j_params "${jname}")"
392   j_up "${jname}" || return 0
393   meh "stopping ${jname} ..."
394
395   # Stop all services in /etc/rcJ.d
396   j_root_eval "${jname}" '[ -d /etc/rcJ.d ] && ( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort -r | sed -e "s/$/ stop/" | sh )'
397
398   # Terminate ipcd
399   j_ipc_stop
400
401   # Unmount filesystems
402   j_fstab_umount
403 }
404
405 # Send a signal to all processes inside a chroot, if possible
406 j_signal() {
407   sig="${1:-HUP}"
408   jname="${2:-${jname}}"
409
410   [ "$(j_cgroup)" ] || { echo "No cgroup support; cannot signal"; return 1; }
411   pids="$(cat "$(j_cgroup)/tasks")"
412   if [ "${pids}" ]
413   then
414     echo Sending ${sig} to ${pids} ...
415     echo "FROZEN" > "$(j_cgroup)/freezer.state"
416     kill -${sig} ${pids}
417     echo "THAWED" > "$(j_cgroup)/freezer.state"
418   fi
419 }
420
421 # Kill all processes inside chroot (TERM, then KILL)
422 j_kill() {
423   jname="${1:-${jname}}"
424   eval "$(j_params "${jname}")"
425   meh "killing ${jname} ..."
426
427   j_signal "TERM" "${jname}"
428   sleep 2
429   j_signal "KILL" "${jname}"
430 }
431
432 # Hook to allow mounting chroot mounts from command line
433 j_mount() {
434   jname="${1:-${jname}}"
435   eval "$(j_params "${jname}")"
436   j_fstab_mount
437 }
438
439 # Hook to allow unmounting chroot mounts from command line
440 j_umount() {
441   jname="${1:-${jname}}"
442   eval "$(j_params "${jname}")"
443   j_fstab_umount
444 }
445
446 case "${cmd}" in
447 init|create) j_init "${jname}" "${@}" ;;
448 ls|list) j_ls ;;
449 status) j_status "${jname}" "${@}" ;;
450 start) j_start "${jname}" ;;
451 shell|enter) j_shell "${jname}" ;;
452 eval) j_eval "${jname}" "${*}" ;;
453 stop) j_stop "${jname}" ;;
454 kill) j_kill "${jname}" ;;
455 mount) j_mount "${jname}" ;;
456 umount) j_umount "${jname}" ;;
457 *) pebkac ;;
458 esac