#!/bin/bash #################################################################################### ### INSTRUCTIONS AT https://github.com/lesderid/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.kernel.org/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="btrfs" # running on another VPS service (experimental) no_digitalocean=0 # NOT EXPOSED NORMALLY: don't prompt continue_without_prompting=0 # NOT EXPOSED NORMALLY: path to metadata service meta_base=http://169.254.169.254/metadata/v1/ ######################################## ### 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 sudo vim git ) 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 ;; --no_digitalocean) no_digitalocean=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() { modprobe loop 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}) [[ $no_digitalocean -eq 1 ]] || mkfs.ext4 -L DOROOT ${doroot_loop} mkfs.${target_filesystem} -L ArchRoot ${archroot_loop} log "Mounting image ..." mkdir -p /d2a/work/{doroot,archroot} mount ${archroot_loop} /d2a/work/archroot if [[ $no_digitalocean -eq 0 ]]; then mount ${doroot_loop} /d2a/work/doroot 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 fi 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 cat > /d2a/work/archroot/etc/resolv.conf <<-EOF # Google IPv6 nameservers nameserver 2001:4860:4860::8888 nameserver 2001:4860:4860::8844 # Google IPv4 nameservers nameserver 8.8.8.8 nameserver 8.8.4.4 EOF 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 if [[ $no_digitalocean -eq 0 ]]; then package_digitalocean_synchronize /d2a/work/archroot/dosync.pkg.tar ${chroot_pacman} -U --noconfirm /dosync.pkg.tar rm /d2a/work/archroot/dosync.pkg.tar fi local authkeys if authkeys="$(wget -qO- -T5 -t1 ${meta_base}public-keys)" && test -z "${authkeys}"; then log "*** WARNING ***" log "SSH public keys are not configured for this droplet." log "PermitRootLogin will be enabled in sshd_config to permit root logins over SSH." log "This is a security risk, as passwords are not as secure as public keys." log "To set up public keys, visit the following URL: https://goo.gl/iEgFRs" log "Remember to remove the PermitRootLogin option from sshd_config after doing so." cat >> /d2a/work/archroot/etc/ssh/sshd_config <<-EOF # This enables password logins to root over SSH. # This is insecure; see https://goo.gl/iEgFRs to set up public keys. PermitRootLogin yes EOF fi 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=0 if [[ $root_device != "/dev/vda" ]]; then root_offset_sectors=$(cat /sys/block/vda/${root_device#/dev/}/start) fi 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 # make sure busybox is installed apt-get -yqq install busybox # 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 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/lesderid/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