commit b3aa89d3be6c4298ec66ec3525d44d5ad14ba050 Author: Gavin Li Date: Tue Jun 16 20:27:21 2015 -0700 initial commit for debian 8 script diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e8019f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.swp +.*.swp diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..3e6373b --- /dev/null +++ b/install.sh @@ -0,0 +1,935 @@ +#!/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 "$@" &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" + +######################################## +### 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 + ;; + --help) + print_help_and_exit + ;; + esac + done + [ -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." +} + +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 + + 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 + + chmod 0755 ${pkgroot}/usr/bin/digitalocean-synchronize + + ( cd ${pkgroot} && tar -cf ${destination} * ) +} + +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 + awk -F: '$1 == "root" || $1 == "nobody"' /etc/shadow \ + > /d2a/work/doroot/etc/shadow + chmod 0600 /d2a/work/doroot/etc/shadow + + 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 ..." + cp /etc/ssh/ssh_host_* /d2a/work/archroot/etc/ssh + 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 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 + 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() { + echo "[$(date)]" "$@" >&2 +} + +fatal() { + log "$@" + log "Exiting." + exit 1 +} + +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 + cmp ${etcdir}/shadow ${etcdir}/shadow.synced && return 0 + # change password + local password=$(awk -F: '$1 == "root" { print $2 }' ${etcdir}/shadow) + usermod -p "${password}" root + [ ${#password} -gt 1 ] && chage -d 0 root + # sync password synced file + rm -f ${etcdir}/shadow.synced + cp ${etcdir}/shadow ${etcdir}/shadow.synced +} + +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 prefix=$(netmask_to_prefix $(curl -sf ${url}ipv4/netmask)) + echo "Address=$(curl -sf ${url}ipv4/address)/${prefix}" + if [ "${type}" != "private" ]; then + echo "Gateway=$(curl -sf ${url}ipv4/gateway)" + fi + fi + if [[ " ${attrs} " =~ " ipv6/ " ]]; then + local prefix=$(curl -sf ${url}ipv6/cidr) + echo "Address=$(curl -sf ${url}ipv6/address)/${prefix}" + if [ "${type}" != "private" ]; then + echo "Gateway=$(curl -sf ${url}ipv6/gateway)" + fi + 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 + grep -q "${sshkeys}" /root/.ssh/authorized_keys || \ + printf '\n%s\n' "${sshkeys}" >> /root/.ssh/authorized_keys + fi + local hostname + if ! test -e /etc/hostname && hostname=$(curl -Ssf ${meta_base}hostname); then + echo "${hostname}" > /etc/hostname + hostname "${hostname}" + 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 + fi + + ip link set dev eth0 up + ip addr add dev eth0 169.254.169.252/30 2>/dev/null || true + if curl -Ssf -m 1 ${meta_base} >/dev/null; then + setup_from_metadata_service + fi +} + +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.0-1 +pkgdesc = DigitalOcean Synchronization (passwords, keys, networks) +url = https://github.com/gh2o/digitalocean-debian-to-arch +arch = any +license = GPL +!!!! + +EMBEDDED