1#!/bin/bash -e
2
3# Copyright (c) 2010 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7# This script installs Debian-derived distributions in a chroot environment.
8# It can for example be used to have an accurate 32bit build and test
9# environment when otherwise working on a 64bit machine.
10# N. B. it is unlikely that this script will ever work on anything other than a
11# Debian-derived system.
12
13usage() {
14  echo "usage: ${0##*/} [-m mirror] [-g group,...] [-s] [-c]"
15  echo "-g group,... groups that can use the chroot unauthenticated"
16  echo "             Default: 'admin' and current user's group ('$(id -gn)')"
17  echo "-m mirror    an alternate repository mirror for package downloads"
18  echo "-s           configure default deb-srcs"
19  echo "-c           always copy 64bit helper binaries to 32bit chroot"
20  echo "-h           this help message"
21}
22
23process_opts() {
24  local OPTNAME OPTIND OPTERR OPTARG
25  while getopts ":g:m:sch" OPTNAME; do
26    case "$OPTNAME" in
27      g)
28        [ -n "${OPTARG}" ] &&
29          chroot_groups="${chroot_groups}${chroot_groups:+,}${OPTARG}"
30        ;;
31      m)
32        if [ -n "${mirror}" ]; then
33          echo "You can only specify exactly one mirror location"
34          usage
35          exit 1
36        fi
37        mirror="$OPTARG"
38        ;;
39      s)
40        add_srcs="y"
41        ;;
42      c)
43        copy_64="y"
44        ;;
45      h)
46        usage
47        exit 0
48        ;;
49      \:)
50        echo "'-$OPTARG' needs an argument."
51        usage
52        exit 1
53        ;;
54      *)
55        echo "invalid command-line option: $OPTARG"
56        usage
57        exit 1
58        ;;
59    esac
60  done
61
62  if [ $# -ge ${OPTIND} ]; then
63    eval echo "Unexpected command line argument: \${${OPTIND}}"
64    usage
65    exit 1
66  fi
67}
68
69
70# Check that we are running as a regular user
71[ "$(id -nu)" = root ] && {
72  echo "Run this script as a regular user and provide your \"sudo\""           \
73       "password if requested" >&2
74  exit 1
75}
76mkdir -p "$HOME/chroot/"
77
78process_opts "$@"
79
80# Error handler
81trap 'exit 1' INT TERM QUIT
82trap 'sudo apt-get clean; tput bel; echo; echo Failed' EXIT
83
84# Install any missing applications that this script relies on. If these packages
85# are already installed, don't force another "apt-get install". That would
86# prevent them from being auto-removed, if they ever become eligible for that.
87# And as this script only needs the packages once, there is no good reason to
88# introduce a hard dependency on things such as dchroot and debootstrap.
89dep=
90for i in dchroot debootstrap; do
91  [ -d /usr/share/doc/"$i" ] || dep="$dep $i"
92done
93[ -n "$dep" ] && sudo apt-get -y install $dep
94sudo apt-get -y install schroot
95
96# Create directory for chroot
97sudo mkdir -p /var/lib/chroot
98
99# Find chroot environments that can be installed with debootstrap
100targets="$(cd /usr/share/debootstrap/scripts
101           ls | grep '^[a-z]*$')"
102
103# Ask user to pick one of the available targets
104echo "The following targets are available to be installed in a chroot:"
105j=1; for i in $targets; do
106  printf '%4d: %s\n' "$j" "$i"
107  j=$(($j+1))
108done
109while :; do
110  printf "Which target would you like to install: "
111  read n
112  [ "$n" -gt 0 -a "$n" -lt "$j" ] >&/dev/null && break
113done
114j=1; for i in $targets; do
115  [ "$j" -eq "$n" ] && { distname="$i"; break; }
116  j=$(($j+1))
117done
118
119# On x86-64, ask whether the user wants to install x86-32 or x86-64
120archflag=
121arch=
122if [ "$(uname -m)" = x86_64 ]; then
123  while :; do
124    echo "You are running a 64bit kernel. This allows you to install either a"
125    printf "32bit or a 64bit chroot environment. %s"                           \
126           "Which one do you want (32, 64) "
127    read arch
128    [ "${arch}" == 32 -o "${arch}" == 64 ] && break
129  done
130  [ "${arch}" == 32 ] && archflag="--arch i386" || archflag="--arch amd64"
131  arch="${arch}bit"
132fi
133target="${distname}${arch}"
134
135# Don't overwrite an existing installation
136[ -d /var/lib/chroot/"${target}" ] && {
137  echo "This chroot already exists on your machine." >&2
138  echo "Delete /var/lib/chroot/${target} if you want to start over." >&2
139  exit 1
140}
141sudo mkdir -p /var/lib/chroot/"${target}"
142
143# Offer to include additional standard repositories for Ubuntu-based chroots.
144alt_repos=
145grep ubuntu.com /usr/share/debootstrap/scripts/"${distname}" >&/dev/null && {
146  while :; do
147    echo "Would you like to add ${distname}-updates and ${distname}-security "
148    echo -n "to the chroot's sources.list (y/n)? "
149    read alt_repos
150    case "${alt_repos}" in
151      y|Y)
152        alt_repos="y"
153        break
154      ;;
155      n|N)
156        break
157      ;;
158    esac
159  done
160}
161
162# Remove stale entry from /etc/schroot/schroot.conf. Entries start
163# with the target name in square brackets, followed by an arbitrary
164# number of lines. The entry stops when either the end of file has
165# been reached, or when the beginning of a new target is encountered.
166# This means, we cannot easily match for a range of lines in
167# "sed". Instead, we actually have to iterate over each line and check
168# whether it is the beginning of a new entry.
169sudo sed -ni '/^[[]'"${target%bit}"']$/,${:1;n;/^[[]/b2;b1;:2;p;n;b2};p'       \
170         /etc/schroot/schroot.conf
171
172# Download base system. This takes some time
173if [ -z "${mirror}" ]; then
174 grep ubuntu.com /usr/share/debootstrap/scripts/"${distname}" >&/dev/null &&
175   mirror="http://archive.ubuntu.com/ubuntu" ||
176   mirror="http://ftp.us.debian.org/debian"
177fi
178 sudo debootstrap ${archflag} "${distname}" /var/lib/chroot/"${target}"        \
179                  "$mirror"
180
181# Add new entry to /etc/schroot/schroot.conf
182grep ubuntu.com /usr/share/debootstrap/scripts/"${distname}" >&/dev/null &&
183  brand="Ubuntu" || brand="Debian"
184if [ -z "${chroot_groups}" ]; then
185  chroot_groups="admin,$(id -gn)"
186fi
187sudo sh -c 'cat >>/etc/schroot/schroot.conf' <<EOF
188[${target%bit}]
189description=${brand} ${distname} ${arch}
190type=directory
191directory=/var/lib/chroot/${target}
192priority=3
193users=root
194groups=${chroot_groups}
195root-groups=${chroot_groups}
196personality=linux$([ "${arch}" != 64bit ] && echo 32)
197script-config=script-${target}
198
199EOF
200
201# Set up a special directory that changes contents depending on the target
202# that is executing.
203sed '/^FSTAB=/s,/mount-defaults",/mount-'"${target}"'",'                       \
204         /etc/schroot/script-defaults |
205  sudo sh -c 'cat >/etc/schroot/script-'"${target}"
206sudo cp /etc/schroot/mount-defaults /etc/schroot/mount-"${target}"
207echo "$HOME/chroot/.${target} $HOME/chroot none rw,bind 0 0" |
208  sudo sh -c 'cat >>/etc/schroot/mount-'"${target}"
209mkdir -p "$HOME/chroot/.${target}"
210
211# Install a helper script to launch commands in the chroot
212sudo sh -c 'cat >/usr/local/bin/'"${target%bit}" <<EOF
213#!/bin/bash
214if [ \$# -eq 0 ]; then
215  exec schroot -c ${target%bit} -p
216else
217  p="\$1"; shift
218  exec schroot -c ${target%bit} -p "\$p" -- "\$@"
219fi
220exit 1
221EOF
222sudo chown root:root /usr/local/bin/"${target%bit}"
223sudo chmod 755 /usr/local/bin/"${target%bit}"
224
225# Add the standard Ubuntu update repositories if requested.
226[ "${alt_repos}" = "y" -a \
227  -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
228sudo sed -i '/^deb .* [^ -]\+ main$/p
229             s/^\(deb .* [^ -]\+\) main/\1-security main/
230             p
231             t1
232             d
233             :1;s/-security main/-updates main/
234             t
235             d' "/var/lib/chroot/${target}/etc/apt/sources.list"
236
237# Add a few more repositories to the chroot
238[ "${add_srcs}" = "y" -a \
239  -r "/var/lib/chroot/${target}/etc/apt/sources.list" ] &&
240sudo sed -i 's/ main$/ main restricted universe multiverse/
241             p
242             t1
243             d
244          :1;s/^deb/deb-src/
245             t
246             d' "/var/lib/chroot/${target}/etc/apt/sources.list"
247
248# Update packages
249sudo schroot -c "${target%bit}" -p -- /bin/sh -c '
250  apt-get update; apt-get -y dist-upgrade' || :
251
252# Install a couple of missing packages
253for i in debian-keyring ubuntu-keyring locales sudo; do
254  [ -d "/var/lib/chroot/${target}/usr/share/doc/$i" ] ||
255    sudo schroot -c "${target%bit}" -p -- apt-get -y install "$i" || :
256done
257
258# Configure locales
259sudo schroot -c "${target%bit}" -p -- /bin/sh -c '
260  l='"${LANG:-en_US}"'; l="${l%%.*}"
261  [ -r /etc/locale.gen ] &&
262    sed -i "s/^# \($l\)/\1/" /etc/locale.gen
263  locale-gen $LANG en_US en_US.UTF-8' || :
264
265# Configure "sudo" package
266sudo schroot -c "${target%bit}" -p -- /bin/sh -c '
267  egrep '"'^$(id -nu) '"' /etc/sudoers >/dev/null 2>&1 ||
268  echo '"'$(id -nu) ALL=(ALL) ALL'"' >>/etc/sudoers'
269
270# Install a few more commonly used packages
271sudo schroot -c "${target%bit}" -p -- apt-get -y install                       \
272  autoconf automake1.9 dpkg-dev g++-multilib gcc-multilib gdb less libtool     \
273  strace
274
275# If running a 32bit environment on a 64bit machine, install a few binaries
276# as 64bit. This is only done automatically if the chroot distro is the same as
277# the host, otherwise there might be incompatibilities in build settings or
278# runtime dependencies. The user can force it with the '-c' flag.
279host_distro=$(grep DISTRIB_CODENAME /etc/lsb-release 2>/dev/null | \
280  cut -d "=" -f 2)
281if [ "${copy_64}" = "y" -o \
282    "${host_distro}" = "${distname}" -a "${arch}" = 32bit ] && \
283    file /bin/bash 2>/dev/null | grep -q x86-64; then
284  readlinepkg=$(sudo schroot -c "${target%bit}" -p -- sh -c \
285    'apt-cache search "lib64readline.\$" | sort | tail -n 1 | cut -d " " -f 1')
286  sudo schroot -c "${target%bit}" -p -- apt-get -y install                     \
287    lib64expat1 lib64ncurses5 ${readlinepkg} lib64z1
288  dep=
289  for i in binutils gdb strace; do
290    [ -d /usr/share/doc/"$i" ] || dep="$dep $i"
291  done
292  [ -n "$dep" ] && sudo apt-get -y install $dep
293  sudo cp /usr/bin/gdb "/var/lib/chroot/${target}/usr/local/bin/"
294  sudo cp /usr/bin/ld "/var/lib/chroot/${target}/usr/local/bin/"
295  for i in libbfd libpython; do
296    lib="$({ ldd /usr/bin/ld; ldd /usr/bin/gdb; } |
297           grep "$i" | awk '{ print $3 }')"
298    if [ -n "$lib" -a -r "$lib" ]; then
299      sudo cp "$lib" "/var/lib/chroot/${target}/usr/lib64/"
300    fi
301  done
302  for lib in libssl libcrypt; do
303    sudo cp /usr/lib/$lib* "/var/lib/chroot/${target}/usr/lib64/" || :
304  done
305fi
306
307# Clean up package files
308sudo schroot -c "${target%bit}" -p -- apt-get clean
309sudo apt-get clean
310
311# Let the user know what we did
312trap '' INT TERM QUIT
313trap '' EXIT
314cat <<EOF
315
316
317Successfully installed ${distname} ${arch}
318
319You can run programs inside of the chroot by invoking the "${target%bit}"
320command.
321
322Your home directory is shared between the host and the chroot. But I configured
323$HOME/chroot to be private to the chroot environment. You can use it
324for files that need to differ between environments.
325EOF
326