468 lines
12 KiB
Bash
Executable File
468 lines
12 KiB
Bash
Executable File
#!/bin/sh
|
|
set -ex
|
|
MYSELF="${0##*/}"
|
|
|
|
bindir="${0%/*}"
|
|
datadir="${bindir%/*}/share/fuel-bootstrap-image"
|
|
|
|
global_conf="/etc/fuel-bootstrap-image.conf"
|
|
[ -r "$global_conf" ] && . "$global_conf"
|
|
|
|
[ -z "$MOS_VERSION" ] && MOS_VERSION="8.0"
|
|
[ -z "$DISTRO_RELEASE" ] && DISTRO_RELEASE="trusty"
|
|
[ -z "$MIRROR_DISTRO" ] && MIRROR_DISTRO="http://archive.ubuntu.com/ubuntu"
|
|
[ -z "$MIRROR_MOS" ] && MIRROR_MOS="http://mirror.fuel-infra.org/mos-repos/ubuntu/$MOS_VERSION"
|
|
[ -z "$KERNEL_FLAVOR" ] && KERNEL_FLAVOR="-generic-lts-trusty"
|
|
[ -z "$ARCH" ] && ARCH="amd64"
|
|
[ -z "$DESTDIR" ] && DESTDIR="/var/www/nailgun/bootstrap/ubuntu"
|
|
|
|
# Packages required for the master node to discover a bootstrap node
|
|
BOOTSTRAP_FUEL_PKGS_DFLT="openssh-client openssh-server ntp mcollective nailgun-agent nailgun-mcagents nailgun-net-check fuel-agent"
|
|
[ -z "$BOOTSTRAP_FUEL_PKGS" ] && BOOTSTRAP_FUEL_PKGS="$BOOTSTRAP_FUEL_PKGS_DFLT"
|
|
|
|
if [ -z "$BOOTSTRAP_IRONIC" ]; then
|
|
GONFIG_SOURCE="$datadir/ubuntu/files/"
|
|
else
|
|
GONFIG_SOURCE="$datadir/ubuntu/files.ironic/"
|
|
fi
|
|
|
|
if [ -n "$http_proxy" ]; then
|
|
export HTTP_PROXY="$http_proxy"
|
|
elif [ -n "$HTTP_PROXY" ]; then
|
|
export http_proxy="$HTTP_PROXY"
|
|
fi
|
|
|
|
# Kernel, firmware, live boot
|
|
BOOTSTRAP_PKGS="ubuntu-minimal live-boot live-boot-initramfs-tools linux-image${KERNEL_FLAVOR} linux-firmware linux-firmware-nonfree"
|
|
# compress initramfs with xz, make squashfs root filesystem image
|
|
BOOTSTRAP_PKGS="$BOOTSTRAP_PKGS xz-utils squashfs-tools"
|
|
# Smaller tools providing the standard ones.
|
|
# - mdadm depends on mail-transport-agent, default one is postfix => use msmtp instead
|
|
BOOTSTRAP_PKGS="$BOOTSTRAP_PKGS msmtp-mta gdebi-core"
|
|
|
|
apt_setup ()
|
|
{
|
|
local root="$1"
|
|
local sources_list="${root}/etc/apt/sources.list"
|
|
local apt_prefs="${root}/etc/apt/preferences"
|
|
local mos_codename="mos${MOS_VERSION}"
|
|
local release_file="$MIRROR_MOS/dists/$mos_codename/Release"
|
|
if ! wget -q -O /dev/null "$release_file" 2>/dev/null; then
|
|
cat >&2 <<-EOF
|
|
$MYSELF: broken MOS repo: no $release_file"
|
|
EOF
|
|
exit 2
|
|
fi
|
|
mkdir -p "${sources_list%/*}"
|
|
|
|
cat > "$sources_list" <<-EOF
|
|
deb $MIRROR_DISTRO ${DISTRO_RELEASE} main universe multiverse restricted
|
|
deb $MIRROR_DISTRO ${DISTRO_RELEASE}-security main universe multiverse restricted
|
|
deb $MIRROR_DISTRO ${DISTRO_RELEASE}-updates main universe multiverse restricted
|
|
deb $MIRROR_MOS ${mos_codename} main
|
|
deb $MIRROR_MOS ${mos_codename}-security main
|
|
deb $MIRROR_MOS ${mos_codename}-updates main
|
|
deb $MIRROR_MOS ${mos_codename}-holdback main
|
|
EOF
|
|
if [ -n "$EXTRA_DEB_REPOS" ]; then
|
|
l="$EXTRA_DEB_REPOS"
|
|
IFS='|'
|
|
set -- $l
|
|
unset IFS
|
|
for repo; do
|
|
echo "$repo"
|
|
done >> "$sources_list"
|
|
fi
|
|
|
|
cat > "$apt_prefs" <<-EOF
|
|
Package: *
|
|
Pin: release o=Mirantis, n=mos${MOS_VERSION}
|
|
Pin-Priority: 1101
|
|
Package: *
|
|
Pin: release o=Mirantis, n=${DISTRO_RELEASE}
|
|
Pin-Priority: 1101
|
|
EOF
|
|
|
|
if [ -n "$HTTP_PROXY" ]; then
|
|
cat > "$root/etc/apt/apt.conf.d/01mirantis-use-proxy" <<-EOF
|
|
Acquire::http::Proxy "$HTTP_PROXY";
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
run_apt_get ()
|
|
{
|
|
local root="$1"
|
|
shift
|
|
chroot "$root" env \
|
|
LC_ALL=C \
|
|
DEBIAN_FRONTEND=noninteractive \
|
|
DEBCONF_NONINTERACTIVE_SEEN=true \
|
|
TMPDIR=/tmp \
|
|
TMP=/tmp \
|
|
apt-get $@
|
|
}
|
|
|
|
run_apt_key ()
|
|
{
|
|
local root="$1"
|
|
shift
|
|
chroot "$root" env \
|
|
LC_ALL=C \
|
|
DEBIAN_FRONTEND=noninteractive \
|
|
DEBCONF_NONINTERACTIVE_SEEN=true \
|
|
TMPDIR=/tmp \
|
|
TMP=/tmp \
|
|
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $@
|
|
}
|
|
|
|
dpkg_is_too_old ()
|
|
{
|
|
# XXX: dpkg-deb versions older than 1.15.6 can't handle data.tar.xz
|
|
# (which is the default payload of Ubuntu packages)
|
|
# Such an ancient version of dpkg is shipped with CentOS 6.[56]
|
|
local dpkg_version
|
|
local dpkg_major_version
|
|
local dpkg_minor_version
|
|
local dpkg_patch_version
|
|
if ! dpkg-deb --help >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
dpkg_version=`dpkg-deb --version | sed -rne '1 s/^.*\s+version\s+([0-9]+)\.([0-9]+)\.([0-9]+).*/\1.\2.\3/p'`
|
|
[ -z "$dpkg_version" ] && return 0
|
|
|
|
IFS='.'
|
|
set -- $dpkg_version
|
|
unset IFS
|
|
dpkg_major_version="$1"
|
|
dpkg_minor_version="$2"
|
|
dpkg_patch_version="$3"
|
|
|
|
if [ $dpkg_major_version -le 1 ] && [ $dpkg_minor_version -le 15 ] && [ $dpkg_patch_version -lt 6 ]; then
|
|
echo "DEBUG: $MYSELF: dpkg is too old, using ar to unpack debian packages" >&2
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
run_debootstrap ()
|
|
{
|
|
local root="$1"
|
|
[ -z "$root" ] && exit 1
|
|
local insecure="--no-check-gpg"
|
|
local extractor=''
|
|
if dpkg_is_too_old; then
|
|
# Ubuntu packages use data.tar.xz payload. Ancient versions of
|
|
# dpkg (in particular the ones shipped with CentOS 6.x) can't
|
|
# handle such packages. Tell debootstrap to use ar instead to
|
|
# avoid the failure.
|
|
extractor='--extractor=ar'
|
|
fi
|
|
env \
|
|
LC_ALL=C \
|
|
DEBIAN_FRONTEND=noninteractive \
|
|
DEBCONF_NONINTERACTIVE_SEEN=true \
|
|
debootstrap $insecure $extractor --arch=${ARCH} ${DISTRO_RELEASE} "$root" $MIRROR_DISTRO
|
|
}
|
|
|
|
install_packages ()
|
|
{
|
|
local root="$1"
|
|
shift
|
|
echo "INFO: $MYSELF: installing pkgs: $*" >&2
|
|
run_apt_get "$root" install --yes $@
|
|
}
|
|
|
|
upgrade_chroot ()
|
|
{
|
|
local root="$1"
|
|
run_apt_key "$root" CA2B20483E301371
|
|
run_apt_get "$root" update
|
|
if ! mountpoint -q "$root/proc"; then
|
|
mount -t proc bootstrapproc "$root/proc"
|
|
fi
|
|
run_apt_get "$root" dist-upgrade --yes
|
|
}
|
|
|
|
add_local_mos_repo ()
|
|
{
|
|
# we need the local APT repo (/var/www/nailgun/ubuntu/x86_64)
|
|
# before web server is up and running => use bind mount
|
|
local root="$1"
|
|
# TODO(asheplyakov): use proper arch name (amd64)
|
|
local local_repo="/var/www/nailgun/ubuntu/x86_64"
|
|
local path_in_chroot="/tmp/local-apt"
|
|
local source_parts_d="${root}/etc/apt/sources.list.d"
|
|
|
|
# TODO(asheplyakov): update the codename after repo get fixed
|
|
local mos_codename="mos${MOS_VERSION}"
|
|
|
|
mkdir -p "${root}${path_in_chroot}" "${source_parts_d}"
|
|
mount -o bind "$local_repo" "${root}${path_in_chroot}"
|
|
mount -o remount,ro,bind "${root}${path_in_chroot}"
|
|
cat > "${source_parts_d}/nailgun-local.list" <<-EOF
|
|
deb file://${path_in_chroot} ${mos_codename} main
|
|
EOF
|
|
}
|
|
|
|
allow_insecure_apt ()
|
|
{
|
|
local root="$1"
|
|
local conflet="${root}/etc/apt/apt.conf.d/02mirantis-insecure-apt"
|
|
mkdir -p "${conflet%/*}"
|
|
echo 'APT::Get::AllowUnauthenticated 1;' > "$conflet"
|
|
}
|
|
|
|
suppress_services_start ()
|
|
{
|
|
local root="$1"
|
|
local policy_rc="$root/usr/sbin/policy-rc.d"
|
|
mkdir -p "${policy_rc%/*}"
|
|
cat > "$policy_rc" <<-EOF
|
|
#!/bin/sh
|
|
# suppress services start in the staging chroot
|
|
exit 101
|
|
EOF
|
|
chmod 755 "$policy_rc"
|
|
}
|
|
|
|
propagate_host_resolv_conf ()
|
|
{
|
|
local root="$1"
|
|
mkdir -p "$root/etc"
|
|
for conf in "/etc/resolv.conf" "/etc/hosts"; do
|
|
if [ -e "${root}${conf}" ]; then
|
|
cp -a "${root}${conf}" "${root}${conf}.bak"
|
|
fi
|
|
done
|
|
}
|
|
|
|
restore_resolv_conf ()
|
|
{
|
|
local root="$1"
|
|
for conf in "/etc/resolv.conf" "/etc/hosts"; do
|
|
if [ -e "${root}${conf}.bak" ]; then
|
|
rm -f "${root}${conf}"
|
|
cp -a "${root}${conf}.bak" "${root}${conf}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
make_utf8_locale ()
|
|
{
|
|
local root="$1"
|
|
chroot "$root" /bin/sh -c "locale-gen en_US.UTF-8 && dpkg-reconfigure locales"
|
|
}
|
|
|
|
# XXX: CentOS version of debootstrap produces a broken /dev:
|
|
# /dev/fd is a directory instead of a symlink to /proc/self/fd
|
|
dev_fixup ()
|
|
{
|
|
local root="$1"
|
|
if [ -z "$root" ]; then
|
|
echo "*** Error: $MYSELF: dev_fixup: \$root is not specified" >&2
|
|
exit 1
|
|
fi
|
|
mkdir -p -m755 "$root/dev"
|
|
if [ ! -L "$root/dev/fd" ]; then
|
|
rm -rf "$root/dev/fd"
|
|
# Ask MAKEDEV to re-create /dev/fd, /dev/stdin, etc
|
|
chroot "$root" /bin/sh -c "cd /dev && MAKEDEV fd"
|
|
fi
|
|
if [ ! -c "$root/dev/null" ]; then
|
|
# basic device nodes are missing => create them
|
|
chroot "$root" /bin/sh -c "cd /dev && MAKEDEV std"
|
|
fi
|
|
}
|
|
|
|
copy_conf_files ()
|
|
{
|
|
local root="$1"
|
|
local sdir="$2"
|
|
rsync -rlptDK "${sdir}" "${root%/}"
|
|
sed -i $root/etc/shadow -e '/^root/c\root:$6$oC7haQNQ$LtVf6AI.QKn9Jb89r83PtQN9fBqpHT9bAFLzy.YVxTLiFgsoqlPY3awKvbuSgtxYHx4RUcpUqMotp.WZ0Hwoj.:15441:0:99999:7:::'
|
|
}
|
|
|
|
install_ssh_keys ()
|
|
{
|
|
local root="$1"
|
|
shift
|
|
if [ -z "$*" ]; then
|
|
echo "*** Error: $MYSELF: no ssh keys specified" >&2
|
|
exit 1
|
|
fi
|
|
local authorized_keys="$root/root/.ssh/authorized_keys"
|
|
local dot_ssh_dir="${authorized_keys%/*}"
|
|
if [ ! -d "${dot_ssh_dir}" ]; then
|
|
mkdir -p -m700 "${dot_ssh_dir}"
|
|
fi
|
|
for key; do
|
|
if [ ! -r "$key" ]; then
|
|
echo "*** Error: $MYSELF: no such file: $key" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
cat $@ > "$authorized_keys"
|
|
chmod 640 "$authorized_keys"
|
|
}
|
|
|
|
cleanup_chroot ()
|
|
{
|
|
local root="$1"
|
|
[ -z "$root" ] && exit 1
|
|
signal_chrooted_processes "$root" SIGTERM
|
|
signal_chrooted_processes "$root" SIGKILL
|
|
# umount "${root}/tmp/local-apt" 2>/dev/null || umount -l "${root}/tmp/local-apt"
|
|
# rm -f "${root}/etc/apt/sources.list.d/nailgun-local.list"
|
|
rm -rf $root/var/cache/apt/archives/*.deb
|
|
rm -f $root/etc/apt/apt.conf.d/01mirantis-use-proxy.conf
|
|
rm -f $root/var/log/bootstrap.log
|
|
rm -rf $root/tmp/*
|
|
rm -rf $root/run/*
|
|
}
|
|
|
|
install_agent ()
|
|
{
|
|
local root="$1"
|
|
local package_path="$2"
|
|
local full_path=`ls $package_path/fuel-agent*.deb`
|
|
local package=`basename $full_path`
|
|
cp $full_path $root/tmp
|
|
chroot "$root" env \
|
|
LC_ALL=C \
|
|
DEBIAN_FRONTEND=noninteractive \
|
|
DEBCONF_NONINTERACTIVE_SEEN=true \
|
|
TMPDIR=/tmp \
|
|
TMP=/tmp \
|
|
gdebi -n /tmp/$package
|
|
|
|
rm -f $root/tmp/$package
|
|
}
|
|
|
|
recompress_initramfs ()
|
|
{
|
|
local root="$1"
|
|
local initramfs_conf="$root/etc/initramfs-tools/initramfs.conf"
|
|
sed -i $initramfs_conf -re 's/COMPRESS\s*=\s*gzip/COMPRESS=xz/'
|
|
rm -f $root/boot/initrd*
|
|
chroot "$root" \
|
|
env \
|
|
LC_ALL=C \
|
|
DEBIAN_FRONTEND=noninteractive \
|
|
DEBCONF_NONINTERACTIVE_SEEN=true \
|
|
TMPDIR=/tmp \
|
|
TMP=/tmp \
|
|
update-initramfs -c -k all
|
|
}
|
|
|
|
mk_squashfs_image ()
|
|
{
|
|
local root="$1"
|
|
local tmp="$$"
|
|
[ -d "$DESTDIR" ] && mkdir -p "$DESTDIR"
|
|
cp -a $root/boot/initrd* $DESTDIR/initramfs.img.${tmp}
|
|
cp -a $root/boot/vmlinuz* $DESTDIR/linux.${tmp}
|
|
rm -f $root/boot/initrd*
|
|
rm -f $root/boot/vmlinuz*
|
|
|
|
# run mksquashfs inside a chroot (Ubuntu kernel will be able to
|
|
# mount an image produced by Ubuntu squashfs-tools)
|
|
mount -t tmpfs -o rw,nodev,nosuid,noatime,mode=0755,size=4M mnt${tmp} "$root/mnt"
|
|
mkdir -p "$root/mnt/src" "$root/mnt/dst"
|
|
mount -o bind "$root" "$root/mnt/src"
|
|
mount -o remount,bind,ro "$root/mnt/src"
|
|
mount -o bind "$DESTDIR" "$root/mnt/dst"
|
|
|
|
if ! mountpoint -q "$root/proc"; then
|
|
mount -t proc sandboxproc "$root/proc"
|
|
fi
|
|
chroot "$root" mksquashfs /mnt/src /mnt/dst/root.squashfs.${tmp} -comp xz -no-progress -noappend
|
|
mv $DESTDIR/initramfs.img.${tmp} $DESTDIR/initramfs.img
|
|
mv $DESTDIR/linux.${tmp} $DESTDIR/linux
|
|
mv $DESTDIR/root.squashfs.${tmp} $DESTDIR/root.squashfs
|
|
|
|
umount "$root/mnt/dst"
|
|
umount "$root/mnt/src"
|
|
umount "$root/mnt"
|
|
}
|
|
|
|
build_image ()
|
|
{
|
|
local root="$1"
|
|
chmod 755 "$root"
|
|
suppress_services_start "$root"
|
|
run_debootstrap "$root"
|
|
dev_fixup "$root"
|
|
suppress_services_start "$root"
|
|
propagate_host_resolv_conf "$root"
|
|
make_utf8_locale "$root"
|
|
apt_setup "$root"
|
|
# add_local_mos_repo "$root"
|
|
allow_insecure_apt "$root"
|
|
upgrade_chroot "$root"
|
|
install_packages "$root" $BOOTSTRAP_PKGS $BOOTSTRAP_FUEL_PKGS
|
|
install_agent "$root" $AGENT_PACKAGE_PATH
|
|
recompress_initramfs "$root"
|
|
copy_conf_files "$root" $GONFIG_SOURCE
|
|
if [ -n "$BOOTSTRAP_SSH_KEYS" ]; then
|
|
install_ssh_keys "$root" $BOOTSTRAP_SSH_KEYS
|
|
else
|
|
cat >&2 <<-EOF
|
|
$MYSELF: Warning: no ssh keys specified
|
|
$MYSELF: bootstrap nodes won't be available via ssh
|
|
EOF
|
|
fi
|
|
restore_resolv_conf "$root"
|
|
cleanup_chroot "$root"
|
|
mk_squashfs_image "$root"
|
|
}
|
|
|
|
root=`mktemp -d --tmpdir fuel-bootstrap-image.XXXXXXXXX`
|
|
|
|
main ()
|
|
{
|
|
build_image "$root"
|
|
}
|
|
|
|
signal_chrooted_processes ()
|
|
{
|
|
local root="$1"
|
|
local signal="${2:-SIGTERM}"
|
|
local max_attempts=10
|
|
local timeout=2
|
|
local count=0
|
|
local found_processes
|
|
[ ! -d "$root" ] && return 0
|
|
while [ $count -lt $max_attempts ]; do
|
|
found_processes=''
|
|
for pid in `fuser $root 2>/dev/null`; do
|
|
[ "$pid" = "kernel" ] && continue
|
|
if [ "`readlink /proc/$pid/root`" = "$root" ]; then
|
|
found_processes='yes'
|
|
kill "-${signal}" $pid
|
|
fi
|
|
done
|
|
[ -z "$found_processes" ] && break
|
|
count=$((count+1))
|
|
sleep $timeout
|
|
done
|
|
}
|
|
|
|
final_cleanup ()
|
|
{
|
|
signal_chrooted_processes "$root" SIGTERM
|
|
signal_chrooted_processes "$root" SIGKILL
|
|
for mnt in /tmp/local-apt /mnt/dst /mnt/src /mnt /proc; do
|
|
if mountpoint -q "${root}${mnt}"; then
|
|
umount "${root}${mnt}" || umount -l "${root}${mnt}" || true
|
|
fi
|
|
done
|
|
if [ -z "$SAVE_TEMPS" ]; then
|
|
rm -rf "$root"
|
|
fi
|
|
}
|
|
|
|
trap final_cleanup 0
|
|
trap final_cleanup HUP TERM INT QUIT
|
|
main
|