digitalocean-debian-to-arch/install.sh

1018 lines
27 KiB
Bash
Executable File

#!/bin/bash
################################################################################
### INSTRUCTIONS AT https://github.com/gh2o/digitalocean-debian-to-arch/ ###
################################################################################
run_from_file() {
local f t
for f in /dev/fd/*; do
[ -h $f ] || continue
[ $f -ef "$0" ] && return
done
t=$(mktemp)
cat > $t
if [ "$(head -n 1 $t)" = '#!/bin/bash' ]; then
chmod +x $t
exec /bin/bash $t "$@" </dev/fd/2
else
rm -f $t
echo "Direct execution not supported with this shell ($_)." >&2
echo "Please try bash instead." >&2
exit 1
fi
}
# do not modify the two lines below
[ -h /dev/fd/0 ] && run_from_file
#!/bin/bash
########################################
### DEFAULT CONFIGURATION ###
########################################
# mirror from which to download archlinux packages
archlinux_mirror="http://mirrors.digitalocean.com/archlinux"
# package to use as kernel (linux or linux-lts)
kernel_package=linux
# migrated machine architecture (x86_64/i686)
target_architecture="$(uname -m)"
# new disklabel type (gpt/dos)
target_disklabel="gpt"
# new filesystem type (ext4/btrfs)
target_filesystem="ext4"
# NOT EXPOSED NORMALLY: don't prompt
continue_without_prompting=0
########################################
### END OF CONFIGURATION ###
########################################
if [ -n "${POSIXLY_CORRECT}" ] || [ -z "${DEBIAN_TO_ARCH_ENV_CLEARED}" ]; then
exec /usr/bin/env -i \
TERM="$TERM" \
PATH=/usr/sbin:/sbin:/usr/bin:/bin \
DEBIAN_TO_ARCH_ENV_CLEARED=1 \
/bin/bash "$0" "$@"
fi
set -eu
set -o pipefail
shopt -s nullglob
shopt -s dotglob
umask 022
sector_size=512
flag_variables=(
archlinux_mirror
kernel_package
target_architecture
target_disklabel
target_filesystem
)
host_packages=(
haveged
parted
)
arch_packages=(
grub
openssh
)
gpt1_size_MiB=1
doroot_size_MiB=6
biosboot_size_MiB=1
archroot_size_MiB=
gpt2_size_MiB=1
doroot_offset_MiB=$((gpt1_size_MiB))
biosboot_offset_MiB=$((doroot_offset_MiB + doroot_size_MiB))
archroot_offset_MiB=$((biosboot_offset_MiB + biosboot_size_MiB))
log() {
echo "[$(date)]" "$@" >&2
}
fatal() {
log "$@"
log "Exiting."
exit 1
}
extract_embedded_file() {
awk -v n="$1" '$0=="!!!!"{p=0};p;$0=="!!!!"n{p=1}' "$0"
}
parse_flags() {
local c conf_key conf_val
while [ $# -gt 0 ]; do
conf_key=
conf_val=
for c in ${flag_variables[@]}; do
case "$1" in
--$c)
shift
[ $# -gt 0 ] || fatal "Option $c requires a value."
conf_key="$c"
conf_val="$1"
shift
break
;;
--$c=*)
conf_key="$c"
conf_val="${1#*=}"
shift
break
;;
--i_understand_that_this_droplet_will_be_completely_wiped)
continue_without_prompting=1
conf_key=option_acknowledged
shift
break
;;
--help)
print_help_and_exit
;;
esac
done
[ "${conf_key}" = option_acknowledged ] && continue
[ -n "${conf_key}" ] || fatal "Unknown option: $1"
[ -n "${conf_val}" ] || fatal "Empty value for option ${conf_key}."
local -n conf_ref=${conf_key}
conf_ref="${conf_val}"
done
log "Configuration:"
for conf_key in ${flag_variables[@]}; do
local -n conf_ref=${conf_key}
log "- ${conf_key} = ${conf_ref}"
done
}
print_help_and_exit() {
local conf_key
echo "Available options: (see script for details)" >&2
for conf_key in ${flag_variables[@]}; do
local -n conf_ref=${conf_key}
echo " --${conf_key}=[${conf_ref}]" >&2
done
exit 1
}
validate_flags_and_augment_globals() {
arch_packages+=(${kernel_package})
case "${target_disklabel}" in
gpt)
;;
dos)
;;
*)
fatal "Unknown disklabel type: ${target_disklabel}"
;;
esac
case "${target_filesystem}" in
ext4)
;;
btrfs)
host_packages+=(btrfs-tools)
arch_packages+=(btrfs-progs)
;;
*)
fatal "Unknown filesystem type: ${target_filesystem}"
;;
esac
local disk_MiB=$(($(cat /sys/block/vda/size) >> 11))
archroot_size_MiB=$((disk_MiB - gpt2_size_MiB - archroot_offset_MiB))
}
read_flags() {
local filename=$1
source ${filename}
}
write_flags() {
local filename=$1
{
local conf_key
for conf_key in ${flag_variables[@]}; do
local -n conf_ref=${conf_key}
printf "%s=%q\n" "${conf_key}" "${conf_ref}"
done
} > ${filename}
}
sanity_checks() {
[ ${EUID} -eq 0 ] || fatal "Script must be run as root."
[ ${UID} -eq 0 ] || fatal "Script must be run as root."
[ -e /dev/vda ] || fatal "Script must be run on a KVM machine."
[[ "$(cat /etc/debian_version)" == 8.? ]] || \
fatal "This script only supports Debian 8.x."
}
prompt_for_destruction() {
(( continue_without_prompting )) && return 0
log "*** ALL DATA ON THIS DROPLET WILL BE WIPED. ***"
log "Please backup all important data on this droplet before continuing."
log 'Type "wipe this droplet" to continue or anything else to cancel.'
local response
read -p ' > ' response
if [ "${response}" = "wipe this droplet" ]; then
return 0
else
log "Cancelled."
exit 0
fi
}
download_and_verify() {
local file_url="$1"
local local_path="$2"
local expected_sha1="$3"
for try in {0..3}; do
if [ ${try} -eq 0 ]; then
[ -e "${local_path}" ] || continue
else
wget -O "${local_path}" "${file_url}"
fi
set -- $(sha1sum "${local_path}")
if [ $1 = "${expected_sha1}" ]; then
return 0
else
rm -f "${local_path}"
fi
done
return 1
}
build_parted_cmdline() {
local cmdline=
local biosboot_name=BIOSBoot
local doroot_name=DORoot
local archroot_name=ArchRoot
if [ ${target_disklabel} = dos ]; then
cmdline="mklabel msdos"
biosboot_name=primary
doroot_name=primary
archroot_name=primary
else
cmdline="mklabel ${target_disklabel}"
fi
local archroot_end_MiB=$((archroot_offset_MiB + archroot_size_MiB))
cmdline+=" mkpart ${doroot_name} ${doroot_offset_MiB}MiB ${biosboot_offset_MiB}MiB"
cmdline+=" mkpart ${biosboot_name} ${biosboot_offset_MiB}MiB ${archroot_offset_MiB}MiB"
cmdline+=" mkpart ${archroot_name} ${archroot_offset_MiB}MiB ${archroot_end_MiB}MiB"
if [ ${target_disklabel} = gpt ]; then
cmdline+=" set 2 bios_grub on"
fi
echo "${cmdline}"
}
setup_loop_device() {
local offset_MiB=$1
local size_MiB=$2
losetup --find --show --offset ${offset_MiB}MiB --size ${size_MiB}MiB /d2a/work/image
}
package_digitalocean_synchronize() {
local destination=$1
local pkgroot=/d2a/work/dosync
local sysdir=${pkgroot}/usr/lib/systemd/system
local netdir=${pkgroot}/usr/lib/systemd/network
mkdir -p ${pkgroot}
extract_embedded_file digitalocean-synchronize.PKGINFO > ${pkgroot}/.PKGINFO
mkdir -p ${pkgroot}/usr/bin
extract_embedded_file digitalocean-synchronize > ${pkgroot}/usr/bin/digitalocean-synchronize
mkdir -p ${sysdir}
extract_embedded_file digitalocean-synchronize.service > ${sysdir}/digitalocean-synchronize.service
mkdir -p ${sysdir}/multi-user.target.wants
ln -s ../digitalocean-synchronize.service ${sysdir}/multi-user.target.wants
mkdir -p ${netdir}
extract_embedded_file 90-virtio-no-rename.link > ${netdir}/90-virtio-no-rename.link
chmod 0755 ${pkgroot}/usr/bin/digitalocean-synchronize
( cd ${pkgroot} && tar -cf ${destination} * )
rm -rf ${pkgroot}
}
kill_processes_in_mountpoint() {
if mountpoint -q $1; then
fuser -kms $1 || true
find /proc -maxdepth 2 -name root -lname $1 | \
grep -o '[0-9]*' | xargs -r kill || true
fi
}
quietly_umount() {
if mountpoint -q $1; then
umount -d $1
fi
}
cleanup_work_directory() {
kill_processes_in_mountpoint /d2a/work/doroot
kill_processes_in_mountpoint /d2a/work/archroot
quietly_umount /d2a/work/doroot
quietly_umount /d2a/work/archroot/var/cache/pacman/pkg
quietly_umount /d2a/work/archroot/dev/pts
quietly_umount /d2a/work/archroot/dev
quietly_umount /d2a/work/archroot/sys
quietly_umount /d2a/work/archroot/proc
quietly_umount /d2a/work/archroot
rm -rf --one-file-system /d2a/work
}
stage1_install_exit() {
set +e
cleanup_work_directory
}
stage1_install() {
trap stage1_install_exit EXIT
cleanup_work_directory
mkdir -p /d2a/work
log "Installing required packages ..."
DEBIAN_FRONTEND=noninteractive apt-get install -y ${host_packages[@]}
log "Partitioning image ..."
local disk_sectors=$(cat /sys/block/vda/size)
rm -f /d2a/work/image
truncate -s $((disk_sectors * sector_size)) /d2a/work/image
parted /d2a/work/image $(build_parted_cmdline)
log "Formatting image ..."
local doroot_loop=$(setup_loop_device ${doroot_offset_MiB} ${doroot_size_MiB})
local archroot_loop=$(setup_loop_device ${archroot_offset_MiB} ${archroot_size_MiB})
mkfs.ext4 -L DOROOT ${doroot_loop}
mkfs.${target_filesystem} -L ArchRoot ${archroot_loop}
log "Mounting image ..."
mkdir -p /d2a/work/{doroot,archroot}
mount ${doroot_loop} /d2a/work/doroot
mount ${archroot_loop} /d2a/work/archroot
log "Setting up DOROOT ..."
mkdir -p /d2a/work/doroot/etc/network
touch /d2a/work/doroot/etc/network/interfaces
cat > /d2a/work/doroot/README <<-EOF
DO NOT TOUCH FILES ON THIS PARTITION.
The DOROOT partition is where DigitalOcean writes passwords and other data
when a droplet is rebuilt from an image or restored from a snapshot.
If certain files are missing, restores/rebuilds will not work and you will
end up with an unusable image.
The digitalocean-synchronize script also watches this partition.
If this partition (particularly etc/shadow) is written to, the script will
reset the root password to the one provided by DigitalOcean and wipe all
SSH host keys for security.
EOF
chmod 0444 /d2a/work/doroot/README
log "Downloading bootstrap tarball ..."
set -- $(wget -qO- ${archlinux_mirror}/iso/latest/sha1sums.txt |
grep "archlinux-bootstrap-[^-]*-${target_architecture}.tar.gz")
local expected_sha1=$1
local bootstrap_filename=$2
download_and_verify \
${archlinux_mirror}/iso/latest/${bootstrap_filename} \
/d2a/bootstrap.tar.gz \
${expected_sha1}
log "Extracting bootstrap tarball ..."
tar -xzf /d2a/bootstrap.tar.gz \
--directory=/d2a/work/archroot \
--strip-components=1
log "Mounting virtual filesystems ..."
mount -t proc proc /d2a/work/archroot/proc
mount -t sysfs sys /d2a/work/archroot/sys
mount -t devtmpfs dev /d2a/work/archroot/dev
mkdir -p /d2a/work/archroot/dev/pts
mount -t devpts pts /d2a/work/archroot/dev/pts
log "Binding packages directory ..."
mkdir -p /d2a/packages
mount --bind /d2a/packages /d2a/work/archroot/var/cache/pacman/pkg
log "Preparing bootstrap filesystem ..."
echo "Server = ${archlinux_mirror}/\$repo/os/\$arch" > /d2a/work/archroot/etc/pacman.d/mirrorlist
echo 'nameserver 8.8.8.8' > /d2a/work/archroot/etc/resolv.conf
log "Installing base system ..."
chroot /d2a/work/archroot pacman-key --init
chroot /d2a/work/archroot pacman-key --populate archlinux
local chroot_pacman="chroot /d2a/work/archroot pacman --arch ${target_architecture}"
${chroot_pacman} -Sy
${chroot_pacman} -Su --noconfirm --needed \
$(${chroot_pacman} -Sgq base | grep -v '^linux$') \
${arch_packages[@]}
log "Configuring base system ..."
hostname > /d2a/work/archroot/etc/hostname
cp /etc/ssh/ssh_host_* /d2a/work/archroot/etc/ssh/
local encrypted_password=$(awk -F: '$1 == "root" { print $2 }' /etc/shadow)
chroot /d2a/work/archroot usermod -p "${encrypted_password}" root
chroot /d2a/work/archroot systemctl enable systemd-networkd.service
chroot /d2a/work/archroot systemctl enable sshd.service
package_digitalocean_synchronize /d2a/work/archroot/dosync.pkg.tar
${chroot_pacman} -U --noconfirm /dosync.pkg.tar
rm /d2a/work/archroot/dosync.pkg.tar
log "Finishing up image generation ..."
ln -f /d2a/work/image /d2a/image
cleanup_work_directory
trap - EXIT
}
bisect_left_on_allocation() {
local alloc_start_sector=$1
local alloc_end_sector=$2
local -n bisection_output=$3
local -n allocation_map=$4
local lo=0 hi=${#allocation_map[@]}
while (( lo < hi )); do
local mid=$(((lo+hi)/2))
set -- ${allocation_map[$mid]}
if (( $# == 0 )) || (( $1 < alloc_start_sector )); then
lo=$((mid+1))
else
hi=$((mid))
fi
done
bisection_output=$lo
}
check_for_allocation_overlap() {
local check_start_sector=$1
local check_end_sector=$2
local -n overlap_start_sector=$3
local -n overlap_end_sector=$4
shift 4
local allocation_maps="$*"
# overlap_end_sector = 0 if no overlap
overlap_start_sector=0
overlap_end_sector=0
local map_name
for map_name in ${allocation_maps}; do
local -n allocation_map=${map_name}
local map_length=${#allocation_map[@]}
(( ${map_length} )) || continue
local bisection_index
bisect_left_on_allocation ${check_start_sector} ${check_end_sector} \
bisection_index ${map_name}
local check_index
for check_index in $((bisection_index - 1)) $((bisection_index)); do
(( check_index < 0 || check_index >= map_length )) && continue
set -- ${allocation_map[${check_index}]}
(( $# == 0 )) && continue
local alloc_start_sector=$1
local alloc_end_sector=$2
(( check_start_sector >= alloc_end_sector || alloc_start_sector >= check_end_sector )) && continue
# overlap detected
overlap_start_sector=$((alloc_start_sector > check_start_sector ?
alloc_start_sector : check_start_sector))
overlap_end_sector=$((alloc_end_sector < check_end_sector ?
alloc_end_sector : check_end_sector))
return
done
done
}
insert_into_allocation_map() {
local -n allocation_map=$1
shift
local alloc_start_sector=$1
local alloc_end_sector=$2
if (( ${#allocation_map[@]} == 0 )); then
allocation_map=("$*")
else
local bisection_index
bisect_left_on_allocation ${alloc_start_sector} ${alloc_end_sector} \
bisection_index ${!allocation_map}
allocation_map=(
"${allocation_map[@]:0:${bisection_index}}"
"$*"
"${allocation_map[@]:${bisection_index}}")
fi
}
stage2_arrange() {
local disk_sectors=$(cat /sys/block/vda/size)
local root_device=$(awk '$2 == "/" { root = $1 } END { print root }' /proc/mounts)
local root_offset_sectors=$(cat /sys/block/vda/${root_device#/dev/}/start)
local srcdst_map=() # original source to target map
local unalloc_map=() # extents not used by either source or target (for tmpdst_map)
local tmpdst_map=() # extents on temporary redirection (allocated from unalloc_map)
local source_start_sector source_end_sector target_start_sector target_end_sector
log "Creating block rearrangement plan ..."
# get and sort extents
filefrag -e -s -v -b${sector_size} /d2a/image | \
sed '/^ *[0-9]*:/!d;s/[:.]/ /g' | \
sort -nk4 > /d2a/imagemap
while read line; do
set -- ${line}
source_start_sector=$(($4 + root_offset_sectors))
source_end_sector=$((source_start_sector + $6))
target_start_sector=$2
target_end_sector=$((target_start_sector + $6))
echo ${source_start_sector} ${source_end_sector}
echo ${target_start_sector} ${target_end_sector}
srcdst_map+=("${source_start_sector} ${source_end_sector} ${target_start_sector}")
done < /d2a/imagemap > /d2a/unsortedallocs
sort -n < /d2a/unsortedallocs > /d2a/sortedallocs
# build map of unallocated sectors
local unalloc_start_sector=0 unalloc_end_sector=${disk_sectors}
while read source_start_sector source_end_sector; do
if (( source_end_sector <= unalloc_start_sector )); then
# does not overlap unallocated part
continue
elif (( source_start_sector > unalloc_start_sector )); then
# full overlap with unallocated part
unalloc_map+=("${unalloc_start_sector} ${source_start_sector}")
unalloc_start_sector=${source_end_sector}
else
# partial overlap
unalloc_start_sector=${source_end_sector}
fi
done < /d2a/sortedallocs
if (( unalloc_start_sector != unalloc_end_sector )); then
unalloc_map+=("${unalloc_start_sector} ${unalloc_end_sector}")
fi
# open blockplan
exec {blockplan_fd}>/d2a/blockplan
# arrange sectors
while (( ${#srcdst_map[@]} )); do
set -- ${srcdst_map[-1]}
source_start_sector=$1
source_end_sector=$2
target_start_sector=$3
target_end_sector=$((target_start_sector + (source_end_sector - source_start_sector)))
if (( source_start_sector == target_start_sector )); then
unset 'srcdst_map[-1]'
continue
elif (( target_start_sector >= source_end_sector ||
source_start_sector >= target_end_sector )); then
unset 'srcdst_map[-1]'
else
local new_extent_sectors=$((target_start_sector - source_start_sector))
new_extent_sectors=${new_extent_sectors#-} # absolute value
set -- \
$((source_start_sector + new_extent_sectors)) \
$((source_end_sector)) \
$((target_start_sector + new_extent_sectors))
srcdst_map[-1]="$*"
source_end_sector=$((source_start_sector + new_extent_sectors))
fi
local overlap_start_sector overlap_end_sector
check_for_allocation_overlap \
${target_start_sector} ${target_end_sector} \
overlap_start_sector overlap_end_sector \
srcdst_map
if (( overlap_end_sector )); then
# insert non-overlapping parts back into srcdst_map
if (( target_start_sector < overlap_start_sector )); then
local nonoverlap_length_sectors=$((overlap_start_sector - target_start_sector))
insert_into_allocation_map srcdst_map \
${source_start_sector} \
$((source_start_sector + nonoverlap_length_sectors)) \
${target_start_sector}
fi
if (( target_end_sector > overlap_end_sector )); then
local nonoverlap_length_sectors=$((target_end_sector - overlap_end_sector))
insert_into_allocation_map srcdst_map \
$((source_end_sector - nonoverlap_length_sectors)) \
${source_end_sector} \
${overlap_end_sector}
fi
# copy overlapping portion into tmpdst_map
while (( overlap_start_sector < overlap_end_sector )); do
set -- ${unalloc_map[-1]}
unset 'unalloc_map[-1]' # or nullglob will eat it up
local unalloc_start_sector=$1
local unalloc_end_sector=$2
local unalloc_length_sectors=$((unalloc_end_sector - unalloc_start_sector))
local overlap_length_sectors=$((overlap_end_sector - overlap_start_sector))
if (( overlap_length_sectors < unalloc_length_sectors )); then
# return unused portion to unalloc_map
unalloc_map+=("${unalloc_start_sector} $((unalloc_end_sector - overlap_length_sectors))")
unalloc_start_sector=$((unalloc_end_sector - overlap_length_sectors))
unalloc_length_sectors=${overlap_length_sectors}
fi
echo >&${blockplan_fd} \
$((source_start_sector + (overlap_start_sector - target_start_sector))) \
${unalloc_start_sector} \
${unalloc_length_sectors}
insert_into_allocation_map tmpdst_map \
${unalloc_start_sector} \
${unalloc_end_sector} \
${overlap_start_sector}
(( overlap_start_sector += unalloc_length_sectors ))
done
else
echo >&${blockplan_fd} \
${source_start_sector} \
${target_start_sector} \
$((source_end_sector - source_start_sector))
fi
done
# restore overlapped sectors
while (( ${#tmpdst_map[@]} )); do
set -- ${tmpdst_map[-1]}
unset 'tmpdst_map[-1]'
source_start_sector=$1
source_end_sector=$2
target_start_sector=$3
echo >&${blockplan_fd} \
${source_start_sector} \
${target_start_sector} \
$((source_end_sector - source_start_sector))
done
# close blockplan
exec {blockplan_fd}>&-
}
cleanup_mid_directory() {
quietly_umount /d2a/mid
rm -rf --one-file-system /d2a/mid
}
add_binary_to_mid() {
mkdir -p $(dirname /d2a/mid/$1)
cp $1 /d2a/mid/$1
ldd $1 | grep -o '/[^ ]* (0x[0-9a-f]*)' | \
while read libpath ignored; do
[ -e /d2a/mid/${libpath} ] && continue
mkdir -p $(dirname /d2a/mid/${libpath})
cp ${libpath} /d2a/mid/${libpath}
done
}
stage3_prepare_exit() {
set +e
cleanup_mid_directory
}
stage3_prepare() {
trap stage3_prepare_exit EXIT
cleanup_mid_directory
mkdir -p /d2a/mid
# mount tmpfs
mount -t tmpfs mid /d2a/mid
# add binaries
add_binary_to_mid /bin/busybox
add_binary_to_mid /bin/bash
# create symlinks
local dir
for dir in bin sbin usr/bin usr/sbin; do mkdir -p /d2a/mid/${dir}; done
ln -s bash /d2a/mid/bin/sh
chroot /d2a/mid /bin/busybox --install
# create directories (will be filled by systemd)
mkdir /d2a/mid/{proc,sys,dev}
# copy in the blockplan
cp /d2a/blockplan /d2a/mid/blockplan
# write out flags
write_flags /d2a/mid/flags
# copy myself
cat "$0" > /d2a/mid/init
chmod 0755 /d2a/mid/init
# detach all loop devices
losetup -D || true
# reboot!
log "The machine will now reboot."
log "Check the console for errors if the machine is still unaccessible after a few minutes."
sleep 1
trap - EXIT
systemctl switch-root /d2a/mid /init
}
stage4_convert_exit() {
log "Error occurred. You're on your own!"
exec /bin/bash </dev/console >/dev/console 2>&1
}
stage4_convert() {
trap stage4_convert_exit EXIT
# unmount old root
local retry
for retry in 1 2 3 4 5; do
if umount /mnt; then
retry=0
break
else
sleep 1
fi
done
if (( retry )); then
umount -rl /mnt
fi
# get total number of sectors
local processed_length=0
local total_length=$(awk '{x+=$3}END{print+x}' /blockplan)
local prev_percentage=-1
local next_percentage=-1
# execute the block plan
local source_sector target_sector extent_length
while read source_sector target_sector extent_length; do
# increment processed length before extent length gets optimized
(( processed_length += extent_length )) || true
# optimize extent length
local transfer_size=${sector_size}
until (( (source_sector & 1) || (target_sector & 1) ||
(extent_length & 1) || (transfer_size >= 0x100000) )); do
(( source_sector >>= 1 , target_sector >>= 1 , extent_length >>= 1,
transfer_size <<= 1 )) || true
done
# do the actual transfer
dd if=/dev/vda of=/dev/vda bs=${transfer_size} \
skip=${source_sector} seek=${target_sector} \
count=${extent_length} 2>/dev/null
# print out the percentage
next_percentage=$((100 * processed_length / total_length))
if (( next_percentage != prev_percentage )); then
printf "\rTransferring blocks ... %s%%" ${next_percentage}
prev_percentage=${next_percentage}
fi
done < /blockplan
echo
# reread partition table
blockdev --rereadpt /dev/vda
# install bootloader
mkdir /archroot
mount /dev/vda3 /archroot
mount -t proc proc /archroot/proc
mount -t sysfs sys /archroot/sys
mount -t devtmpfs dev /archroot/dev
chroot /archroot grub-mkconfig -o /boot/grub/grub.cfg
chroot /archroot grub-install /dev/vda
umount /archroot/dev
umount /archroot/sys
umount /archroot/proc
umount /archroot
# we're done!
sync
reboot -f
}
reinstall_digitalocean_synchronize() {
local package_file=$(mktemp --suffix=.pkg.tar)
package_digitalocean_synchronize ${package_file}
pacman -U --noconfirm ${package_file}
rm ${package_file}
}
if [ -e /var/lib/pacman ]; then
if [ $# -eq 0 ]; then
reinstall_digitalocean_synchronize
else
log "Run this script to install/update the digitalocean-synchronize package."
fi
exit 0
fi
if [ $$ -ne 1 ]; then
parse_flags "$@"
sanity_checks
validate_flags_and_augment_globals
prompt_for_destruction
stage1_install
stage2_arrange
stage3_prepare
else
read_flags /flags
validate_flags_and_augment_globals
stage4_convert
fi
exit 0
cat <<'EMBEDDED'
!!!!digitalocean-synchronize
#!/bin/bash
meta_base=http://169.254.169.254/metadata/v1/
set -eu
set -o pipefail
shopt -s nullglob
shopt -s dotglob
umask 022
log() {
logger -t digitalocean-synchronize "$@" || \
echo "[$(date)]" "$@" >&2
}
netmask_to_prefix() {
local pfx=0 cmp msk
for cmp in ${1//./ } 0; do
for msk in 128 64 32 16 8 4 2 1; do
if (( cmp & msk )); then
(( pfx += 1 ))
else
echo ${pfx}
return
fi
done
done
}
update_shadow_if_changed() {
local etcdir=$1/etc
mkdir -p ${etcdir} || return 0
if [ -e ${etcdir}/shadow ]; then
# change password if file was touched
local encrypted_password=$(awk -F: '$1 == "root" { print $2 }' ${etcdir}/shadow)
if [ "${encrypted_password}" != "z" ]; then
log "Snapshot restore detected."
usermod -p "${encrypted_password}" root
if [ ${#encrypted_password} -gt 1 ]; then
chage -d 0 root
fi
log "Password has been reset."
rm -f /etc/ssh/ssh_host_key /etc/ssh/ssh_host_*_key
log "SSH host keys will be regenerated."
fi
fi
cat > ${etcdir}/shadow <<-EOF
root:z:1::::::
nobody:z:1::::::
EOF
chmod 0600 ${etcdir}/shadow
}
process_interface() {
local url=$1
local attrs=$2
local mac=$(curl -Ssf ${url}mac)
local type=$(curl -Ssf ${url}type)
local interface=
local cand path
for cand in $(ls /sys/class/net); do
path=/sys/class/net/${cand}/address
if [ -e ${path} ] && [ "$(<${path})" = "${mac}" ]; then
interface=${cand}
break
fi
done
[ -n "${interface}" ] || return 0
mkdir -p /run/systemd/network
{
cat <<-EOF
# Generated by digitalocean-synchronize
[Match]
Name=${interface}
[Network]
EOF
if [[ " ${attrs} " =~ " ipv4/ " ]]; then
local address=$(curl -sf ${url}ipv4/address)
local prefix=$(netmask_to_prefix $(curl -sf ${url}ipv4/netmask))
echo "Address=${address}/${prefix}"
if [ "${type}" != "private" ]; then
echo "Gateway=$(curl -sf ${url}ipv4/gateway)"
fi
log "Added IPv4 address ${address}/${prefix} on ${interface}."
fi
if [[ " ${attrs} " =~ " ipv6/ " ]]; then
local address=$(curl -sf ${url}ipv6/address)
local prefix=$(curl -sf ${url}ipv6/cidr)
echo "Address=${address}/${prefix}"
if [ "${type}" != "private" ]; then
echo "Gateway=$(curl -sf ${url}ipv6/gateway)"
fi
log "Added IPv6 address ${address}/${prefix} on ${interface}."
fi
local network_tail=/etc/systemd/network/template/dosync-${interface}.network.tail
if [[ -r "${network_tail}" ]]; then
cat ${network_tail}
log "Appended user specified config for ${interface}."
fi
} > /run/systemd/network/dosync-${interface}.network
}
traverse_interfaces() {
local url=$1
set -- $(curl -Ssf ${url})
if [[ " $* " =~ " mac " ]]; then
process_interface ${url} "$*"
else
local dir
for dir in $*; do
# only want dirs with slash suffix
[ "${dir}" = "${dir%/}" ] && continue
traverse_interfaces ${url}${dir}
done
fi
}
setup_from_metadata_service() {
local sshkeys
if sshkeys=$(curl -Ssf ${meta_base}public-keys) && test -n "${sshkeys}"; then
[ -d /root/.ssh ] || mkdir -m 0700 /root/.ssh
[ -e /root/.ssh/authorized_keys ] || touch /root/.ssh/authorized_keys
if ! grep -q "${sshkeys}" /root/.ssh/authorized_keys; then
printf '\n%s\n' "${sshkeys}" >> /root/.ssh/authorized_keys
log "Added SSH public keys from metadata service."
fi
fi
local hostname
if ! test -e /etc/hostname && hostname=$(curl -Ssf ${meta_base}hostname); then
echo "${hostname}" > /etc/hostname
hostname "${hostname}"
log "Hostname set to ${hostname} from metadata service."
fi
traverse_interfaces ${meta_base}interfaces/
}
digitalocean_synchronize() {
if test -e /dev/disk/by-label/DOROOT && mkdir -p /mnt/doroot; then
mount /dev/disk/by-label/DOROOT /mnt/doroot
update_shadow_if_changed /mnt/doroot
umount /mnt/doroot
else
log "Unable to check DOROOT for snapshot check!"
fi
ip link set dev eth0 up
ip addr add dev eth0 169.254.169.252/30 2>/dev/null || true
local retry
for retry in {1..20}; do
log "Attempting to connect to metadata service ..."
if curl -Ssf -m 1 ${meta_base} >/dev/null; then
setup_from_metadata_service
break
else
log "Unable to connect to metadata service!"
sleep 1
fi
done
ip addr del dev eth0 169.254.169.252/30 2>/dev/null || true
}
digitalocean_synchronize
!!!!
!!!!digitalocean-synchronize.service
[Unit]
Description=DigitalOcean Synchronization
DefaultDependencies=no
Before=systemd-networkd.service
After=systemd-udevd.service
[Service]
Type=oneshot
ExecStart=/usr/sbin/digitalocean-synchronize
!!!!
!!!!digitalocean-synchronize.PKGINFO
pkgname = digitalocean-synchronize
pkgver = 2.4-2
pkgdesc = DigitalOcean Synchronization (passwords, keys, networks)
url = https://github.com/gh2o/digitalocean-debian-to-arch
arch = any
license = GPL
!!!!
!!!!90-virtio-no-rename.link
# Prevent virtio network devices from being renamed.
[Match]
Driver=virtio_net
[Link]
NamePolicy=kernel
!!!!
EMBEDDED