]> CyberLeo.Net >> Repos - CDN/j.git/blob - j
j: kill daemon and remove pidfile/socket on startup
[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 EOF
35   exit 1
36 }
37
38 cmd="$(basename "${0}")"
39 jarch="${J_ARCH:-$(uname -m)}"
40 jbase="${J_BASE:-$(realpath "$(dirname "${0}")/../")}"
41 jname="${J_NAME:-$(basename "${1}")}" #"
42 juser="${J_USER}"
43
44 # cgroup to use; if not found, cgroup support will not be used
45 jcgroup="freezer"
46 # cgroup parent to use (${cgroup}/${parent}/${jname})
47 jcgparent="j"
48
49 # Remove chroot name from argument stack, if passed in
50 [ "${J_NAME}" ] || shift
51
52 # Propagate certain environment variables; sterilize the rest of the environment
53 jenv="
54   LANG=${LANG}
55   TERM=${TERM}
56   USER=${USER}
57 "
58
59 # Debian-specific init: prepare Debian chroot with debootstrap
60 j_init_debian() {
61   jdir="${1}"
62   suite="$(echo "${2:-squeeze}" | tr 'A-Z' 'a-z')"
63
64   # Validation
65   [ "$(which debootstrap 2>&-)" ] || pebkac "j_init_debian: debootstrap not found"
66   [ "${jdir}" ] || pebkac "j_init_debian: jdir must be specified"
67   [ ! -d "${jdir}" ] || pebkac "j_init_debian: jdir must not exist ('${jdir}')"
68   [ -d "$(dirname "${jdir}")" ] || pebkac "j_init_debian: parent of jdir must exist ('${jdir}')"
69
70   # Figure out arch
71   case "${jarch}" in
72   x86|i386) arch=i386 ;;
73   amd64|x86_64|x64) arch=amd64 ;;
74   *) pebkac "Unsupported arch '${jarch}'" ;;
75   esac
76
77   cmd="debootstrap --arch=${arch} --include=curl,file,less,locales,sudo,build-essential,libreadline-dev,zlib1g-dev '${suite}' '${jdir}'"
78   echo "Executing ${cmd}"
79   eval "${cmd}"
80
81   # Make sure locales are generated on first start
82   mkdir -p "${jdir}/etc/rcJ.d"
83   cat > "${jdir}/etc/rcJ.d/S00localegen" <<"EOF"
84 #!/bin/sh
85 /bin/sed -i '/en_US/s/^# //' /etc/locale.gen
86 /usr/sbin/locale-gen
87 /bin/rm -f "${0}"
88 EOF
89   chmod 755 "${jdir}/etc/rcJ.d/S00localegen"
90 }
91
92 # Gentoo-specific init: prepare Gentoo chroot with stage3+portage tarballs
93 j_init_gentoo() {
94   arch="$(uname -m)"
95   base="http://distfiles.gentoo.org/releases/${arch}/autobuilds"
96   pointer="${base}/latest-stage3.txt"
97   # Fetch stage3
98   # Fetch portage
99   # Validate signatures
100   # Unpack stage3
101   # Unpack portage
102 }
103
104 # Create a new chroot, somehow
105 j_init() {
106   # Make sure this does NOT exist
107   jname="${1:-jname}"
108   dist="$(echo "${2:-debian}" | tr 'A-Z' 'a-z')"
109   jdir="${jbase}/${jname}"
110   [ -d "${jdir}" ] && pebkac "j_init: ${jname} already exists"
111   shift 2
112   case "${dist}" in
113   debian) j_init_debian "${jdir}" "${@}" ;;
114   gentoo) j_init_gentoo "${jdir}" "${@}" ;;
115   *) pebkac "Unsupported distro '${dist}'" ;;
116   esac
117 }
118
119 # Figure out and set chroot parameters; needed for all functions that follow
120 j_params() {
121   ( jname="${1:-jname}"
122
123     # Make sure jname is not empty
124     if [ -z "${jname}" ]
125     then
126       printf "jerror='%s'\n" "jname empty"
127       return 1
128     fi
129
130     # Given a chroot name, find and set up the chroot dir
131     jdir="${jbase}/${jname}"
132     jroot="${jdir}/root"
133     if [ ! -d "${jdir}" -o ! -d "${jroot}" ]
134     then
135       printf "jerror='%s'\n" "not a directory"
136       return 1
137     fi
138
139     # Where is the shell?
140     jshell=""
141     for shell in /bin/bash /usr/bin/bash /usr/local/bin/bash /bin/sh
142     do
143       if [ -f "${jroot}/${shell}" ]
144       then
145         jshell=${shell}
146         break
147       fi
148     done
149     if [ -z "${jshell}" ]
150     then
151       printf "jerror='%s'\n" "unable to locate usable shell; is this a jail?"
152       return 1
153     fi
154
155     printf "jerror='' jname='%s' jdir='%s' jroot='%s' jshell='%s'\n" "${jname}" "${jdir}" "${jroot}" "${jshell}"
156   )
157 }
158
159 # Find the 'freezer' cgroup, to determine whether the chroot can be further isolated
160 j_locate_cgroup_mount() {
161   cgroup="${1:-freezer}"
162   cat /proc/mounts | while read src dir type opts fsck dump extra
163   do
164     [ "${type}" = "cgroup" ] || continue
165     oldifs="${IFS}"
166     IFS=','
167     set -- ${opts}
168     IFS=${oldifs}
169     while [ "${1}" ]
170     do
171       [ "${1}" = "${cgroup}" ] && echo "${dir}" && break 2
172       shift
173     done
174   done
175 }
176
177 # Cache and expose cgroup mountpoint for given chroot; make sure it exists
178 j_cgroup() {
179   [ "${jcg}" ] && { echo "${jcg}"; return; }
180   jcg="$(j_locate_cgroup_mount "${cgroup}")/${jcgparent}/${jname}"
181   mkdir -p "${jcg}"
182   echo "${jcg}"
183 }
184
185 j_cg_trap() {
186   [ "$(j_cgroup)" ] || return
187   while [ "${1}" ]
188   do
189     echo "${1}" > "$(j_cgroup)/tasks"
190     shift
191   done
192 }
193
194 # Run a command within a cgroup, if available
195 j_cg_eval() {
196   j_cg="$(j_cgroup)"
197   if [ "$(j_cgroup)" ]
198   then
199     sh -ex <<EOF
200 echo $$ > "$(j_cgroup)/tasks"
201 eval "${*}"
202 EOF
203   else
204     eval "${*}"
205   fi
206 }
207
208 # Copy ipcc into the jail and add helpful symlinks, so it can communicate
209 # simple commands to the outside
210 j_ipc_setup() {
211   ipcc="$(dirname "${0}")/ipcc"
212   cp -f "${ipcc}" "${jroot}/bin"
213   ( cd "${jroot}/bin"
214     [ -e ee ] || ln -s ipcc ee
215     [ -e ff ] || ln -s ipcc ff
216     [ -e gitk ] || ln -s ipcc gitk
217     [ -e gitka ] || ln -s ipcc gitka
218   )
219 }
220
221 # Launch ipcd on jail
222 j_ipc_start() {
223   ipcd_pid="${jdir}/ipcd.pid"
224   # Kill any running IPCd; remove stale pid file and socket
225   if [ -f "${ipcd_pid}" ]
226   then
227     pid="$(cat "${ipcd_pid}")"
228     if [ "${pid}" ]
229     then
230       if ps ouser=,command= "${pid}" | grep "^${ORIG_USER}" | grep -q "ipcd"
231       then
232         omg "IPCd: already running! Killing"
233         kill -TERM "${pid}"
234       fi
235       omg "IPCd: removing stale pid file"
236       rm -f "${ipcd_pid}" "${jroot}/tmp/jipc"
237     fi
238   fi
239   su "${ORIG_USER}" "${jbase}/j/ipcd" "${jdir}" &
240   pid="${!}"
241   echo "${pid}" > "${ipcd_pid}"
242   j_cg_trap "${pid}"
243 }
244
245 # Stop ipcd on jail
246 j_ipc_stop() {
247   ipcd_pid="${jdir}/ipcd.pid"
248   [ -f "${ipcd_pid}" ] && pid="$(cat "${ipcd_pid}")"
249   [ "${pid}" ] && kill -TERM ${pid}
250   rm -f "${ipcd_pid}"
251 }
252
253 # Is this a chroot?
254 j_is() {
255   eval $(j_params "${1}")
256   [ "${jerror}" ] && return 1 || return 0
257 }
258
259 # List available chroots
260 j_ls() {
261   ( cd "${jbase}"; ls -1 ) | while read jname
262   do
263     j_is "${jname}" && echo "${jname}"
264   done
265 }
266
267 # Chroot is 'up' if /dev/pts and /proc are mounted
268 j_up() {
269   jname="${1:-${jname}}"
270   eval "$(j_params "${jname}")"
271   [ "${jerror}" ] && wtf "${jerror}"
272   grep -q "^devpts ${jroot}/dev/pts devpts" /proc/mounts || return 1
273   grep -q "^proc ${jroot}/proc proc" /proc/mounts || return 1
274   return 0
275 }
276
277 # Poll chroot status (j_up)
278 j_status() {
279   [ -z "${1}" ] && set - $(j_ls)
280   while [ "${1}" ]
281   do
282     j_up "${1}" && meh "$(printf '\033[1;32mup\033[0m')" || meh "$(printf '\033[1;31mdown\033[0m')"
283     shift
284   done
285 }
286
287 # Mount /dev/pts and /proc in the chroot
288 j_start() {
289   jname="${1:-${jname}}"
290   j_up "${jname}" && return 0
291   eval "$(j_params "${jname}")"
292   meh "starting ${jname} ..."
293   mount -t devpts devpts "${jroot}/dev/pts"
294   mount -t proc proc "${jroot}/proc"
295
296   # Copy in ipcc and launch ipcd
297   j_ipc_setup
298   j_ipc_start
299
300   # Start all services in /etc/rcJ.d
301   j_root_eval "${jname}" '[ -d /etc/rcJ.d ] && ( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort | sed -e "s/$/ start/" | sh )'
302 }
303
304 # Execute command in chroot as root
305 j_root_eval() {
306   jname="${1:-${jname}}"; shift
307   j_up "${jname}" || wtf "chroot not running"
308   eval "$(j_params "${jname}")"
309   j_cg_trap $$
310   env -i ${jenv} /usr/bin/chroot "${jroot}" /bin/sh -c "${*}"
311 }
312
313 # Execute command in chroot
314 j_eval() {
315   jname="${1:-${jname}}"; shift
316   j_up "${jname}" || wtf "chroot not running"
317   eval "$(j_params "${jname}")"
318   j_cg_trap $$
319   env -i ${jenv} /usr/bin/chroot "${jroot}" /bin/su "${juser:-${USER}}" -c "${*}"
320 }
321
322 j_shell() {
323   jname="${1:-${jname}}"
324   eval "$(j_params "${jname}")"
325   j_eval "${jname}" "cd; exec ${jshell} -l"
326 }
327
328 # Unmount /dev/pts and /proc in the chroot
329 j_stop() {
330   jname="${1:-${jname}}"
331   eval "$(j_params "${jname}")"
332   j_up "${jname}" || return 0
333   meh "stopping ${jname} ..."
334
335   # Stop all services in /etc/rcJ.d
336   j_root_eval "${jname}" '[ -d /etc/rcJ.d ] && ( ls -1 /etc/rcJ.d/* 2>&- | grep /S | sort -r | sed -e "s/$/ stop/" | sh )'
337
338   # Terminate ipcd
339   j_ipc_stop
340
341   umount "${jroot}/proc"
342   umount "${jroot}/dev/pts"
343 }
344
345 # Send a signal to all processes inside a chroot, if possible
346 j_signal() {
347   sig="${1:-HUP}"
348   jname="${2:-${jname}}"
349
350   [ "$(j_cgroup)" ] || { echo "No cgroup support; cannot signal"; return 1; }
351   pids="$(cat "$(j_cgroup)/tasks")"
352   if [ "${pids}" ]
353   then
354     echo Sending ${sig} to ${pids} ...
355     echo "FROZEN" > "$(j_cgroup)/freezer.state"
356     kill -${sig} ${pids}
357     echo "THAWED" > "$(j_cgroup)/freezer.state"
358   fi
359 }
360
361 j_kill() {
362   jname="${1:-${jname}}"
363   eval "$(j_params "${jname}")"
364   meh "killing ${jname} ..."
365
366   j_signal "TERM" "${jname}"
367   sleep 2
368   j_signal "KILL" "${jname}"
369 }
370
371 case "${cmd}" in
372 init|create) j_init "${jname}" "${@}" ;;
373 ls|list) j_ls ;;
374 status) j_status "${jname}" "${@}" ;;
375 start) j_start "${jname}" ;;
376 shell|enter) j_shell "${jname}" ;;
377 eval) j_eval "${jname}" "${*}" ;;
378 stop) j_stop "${jname}" ;;
379 kill) j_kill "${jname}" ;;
380 *) pebkac ;;
381 esac