diff options
-rw-r--r-- | .SRCINFO | 15 | ||||
-rw-r--r-- | .gitignore | 8 | ||||
-rw-r--r-- | 90-dosync-virtio-no-rename.link | 7 | ||||
-rw-r--r-- | Makefile | 30 | ||||
-rw-r--r-- | PKGBUILD | 30 | ||||
-rw-r--r-- | README.md | 79 | ||||
-rw-r--r-- | digitalocean-synchronize.install | 4 | ||||
-rw-r--r-- | digitalocean-synchronize.service | 3 | ||||
-rw-r--r-- | digitalocean-synchronize.sh (renamed from digitalocean-synchronize) | 6 | ||||
-rwxr-xr-x | install.sh | 802 |
10 files changed, 47 insertions, 937 deletions
@@ -1,15 +1,18 @@ pkgbase = digitalocean-synchronize pkgdesc = DigitalOcean Synchronization (passwords, keys, networks) - pkgver = 2.4 - pkgrel = 2 + pkgver = 2.6 + pkgrel = 1 url = https://github.com/gh2o/digitalocean-debian-to-arch - install = digitalocean-synchronize.install arch = any license = GPL - source = digitalocean-synchronize + depends = wget + options = !strip + source = digitalocean-synchronize.sh source = digitalocean-synchronize.service - sha256sums = 2115bcf34d80186103e4399f5a20d410145ee50d316a67bdfe6f43c4b11d2064 - sha256sums = 5888d367a08604b17528d58aa26050209d8ececf7ed35f90b5e96b31165b6a1c + source = 90-dosync-virtio-no-rename.link + sha256sums = 37261e4f5a79a5308e8e94925a037cc2e3d13fa5a473f6fc9b57bed07c06ed5d + sha256sums = 0e51944270c52293f81ea63cb73af42f93341009ddf714ca3a7afe9d4d15a2a8 + sha256sums = d85cde96e602a4ff296d18a7769c683a66feffe5db35a03cdeab651922681f85 pkgname = digitalocean-synchronize diff --git a/.gitignore b/.gitignore index c57f3cc85950..41bbaa7d4257 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ .swp .*.swp -*pkg.tar* -/install.pkg.sh -*.src.tar.gz + +# generated by makepkg +/pkg/ +/src/ +*.pkg.tar.* diff --git a/90-dosync-virtio-no-rename.link b/90-dosync-virtio-no-rename.link new file mode 100644 index 000000000000..5b432b60af71 --- /dev/null +++ b/90-dosync-virtio-no-rename.link @@ -0,0 +1,7 @@ +# Prevent virtio network devices from being renamed. + +[Match] +Driver=virtio_net + +[Link] +NamePolicy=kernel diff --git a/Makefile b/Makefile deleted file mode 100644 index 30089ce1572f..000000000000 --- a/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -PKGNAME = $(shell grep ^pkgname PKGBUILD | sed -e 's:.*=::') -PKGVER = $(shell grep ^pkgver PKGBUILD | sed -e 's:.*=::') -PKGREL = $(shell grep ^pkgrel PKGBUILD | sed -e 's:.*=::') -PKGARCH = $(shell grep ^arch PKGBUILD | sed -e 's:.*=::') - -PKG = $(PKGNAME)-$(PKGVER)-$(PKGREL)-$(PKGARCH).pkg.tar.xz -PKG_SRC = $(PKGNAME)-$(PKGVER)-$(PKGREL).src.tar.gz - -DEPS = digitalocean-synchronize \ - digitalocean-synchronize.service \ - digitalocean-synchronize.install \ - PKGBUILD \ - Makefile - -install.pkg.sh: $(PKG) - @cat $(subst .pkg,,$@) > $@ - @echo -e '\ncat <<EMBEDDED\n\n!!!!digitalocean-synchronize.pkg.tar.xz' >> $@ - @base64 $< >> $@ - @echo -e '!!!!\n\nEMBEDDED' >> $@ - - $(info Build complete!) - $(info Output file: $@) - -$(PKG): $(DEPS) - updpkgsums - mksrcinfo - makepkg -fc - -clean: - rm -f $(PKG) install.pkg.sh @@ -2,21 +2,31 @@ # Contributor: Kyle Manna <kyle at kylemanna dot com> pkgname=digitalocean-synchronize -pkgver=2.4 -pkgrel=2 +pkgver=2.6 +pkgrel=1 pkgdesc='DigitalOcean Synchronization (passwords, keys, networks)' url='https://github.com/gh2o/digitalocean-debian-to-arch' -arch=any -license=GPL -install=digitalocean-synchronize.install -source=('digitalocean-synchronize' - 'digitalocean-synchronize.service') +arch=(any) +license=(GPL) +options=(!strip) -sha256sums=('2115bcf34d80186103e4399f5a20d410145ee50d316a67bdfe6f43c4b11d2064' - '5888d367a08604b17528d58aa26050209d8ececf7ed35f90b5e96b31165b6a1c') +depends=(wget) + +source=(digitalocean-synchronize.sh + digitalocean-synchronize.service + 90-dosync-virtio-no-rename.link) + +sha256sums=('37261e4f5a79a5308e8e94925a037cc2e3d13fa5a473f6fc9b57bed07c06ed5d' + '0e51944270c52293f81ea63cb73af42f93341009ddf714ca3a7afe9d4d15a2a8' + 'd85cde96e602a4ff296d18a7769c683a66feffe5db35a03cdeab651922681f85') package() { - install -Dm755 digitalocean-synchronize ${pkgdir}/usr/bin/digitalocean-synchronize + install -Dm755 digitalocean-synchronize.sh ${pkgdir}/usr/bin/digitalocean-synchronize install -Dm644 digitalocean-synchronize.service ${pkgdir}/usr/lib/systemd/system/digitalocean-synchronize.service + install -Dm644 90-dosync-virtio-no-rename.link ${pkgdir}/usr/lib/systemd/network/90-dosync-virtio-no-rename.link + + local wantsdir=${pkgdir}/usr/lib/systemd/system/multi-user.target.wants + install -dm755 ${wantsdir} + ln -s ../digitalocean-synchronize.service ${wantsdir}/ } diff --git a/README.md b/README.md deleted file mode 100644 index 3b3172054a47..000000000000 --- a/README.md +++ /dev/null @@ -1,79 +0,0 @@ -DigitalOcean Debian to Arch -=========================== -DigitalOcean deprecated Arch Linux a while back because it was relatively -difficult to support due to the rolling updates. I wrote this script to -bring it back! This script downloads a bootstrap Arch Linux image, updates it -to the latest version, then overwrites the host operating system with it. -Unlike Debian 7.x, Debian 8.x on DigitalOcean boots traditionally (through the -MBR and Grub), so no dirty *kexec* magic is needed. - -Warning / Disclaimer --------------------- -<h3>ALL DATA ON THE DROPLET WILL BE UNCONDITIONALLY DESTROYED.</h3> -This script may cause your VPS to become unbootable. -I only recommend running this script on newly created droplets with no -important data. - -Installation ------------- -1. Create a new Debian 8.x droplet (either 32-bit or 64-bit is fine). -2. In the droplet, run the following as root: - `wget https://raw.githubusercontent.com/gh2o/digitalocean-debian-to-arch/debian8/install.sh && bash install.sh` -3. Follow the instructions when prompted. -4. Sit back and relax! The system will automatically reboot once complete, - and you should have a fully updated Arch Linux system in within minutes. - -Advanced Configuration ----------------------- -This script supports several flags, all of which are optional. - -* `--archlinux_mirror` - The Arch Linux mirror from which the bootstrap image and packages should be - downloaded. Defaults to the DigitalOcean mirror at - http://mirrors.digitalocean.com/archlinux. -* `--kernel_package` - The kernel package to install. Defaults to the vanilla `linux` package. - Other options include `linux-lts` for long term support and `linux-grsec` for - a kernel with grsecurity/PaX patches. -* `--target_architecture` - The architecture of the new Arch Linux installation. Defaults to the - architecture of the original Debian image as provided by `uname -m`. - A 64-bit Debian image may convert to either `x86_64` or `i686`. - A 32-bit Debian image may only convert to `i686`. -* `--target_disklabel` - The type of partition table to use. Defaults to `gpt` (GUID partition table - as used by EFI). The alternative is `dos` (traditional MBR). -* `--target_filesystem` - The filesystem on which the Arch Linux installation should be installed. - Defaults to `ext4`. The alternative is `btrfs`. - -How it Works ------------- -1. A sparse disk image is created with the same size of the droplet's disk. -2. Three partitions are made and formatted. - * **DORoot**: A "dummy" partition to keep DigitalOcean happy. When snapshots - are restored, new passwords are written here. - * **BIOSBoot**: The virtual machine BIOS cannot boot from GPT partitions - directly, so a small partition is placed here for bootloader code. - * **ArchRoot**: The main root filesystem for Arch Linux. -3. The Arch Linux bootstrap image is downloaded and unpacked onto ArchRoot. -4. `pacman -Syu` is called inside the image to pull in all the base packages - along with OpenSSH. -5. The root password and SSH host keys are copied into the image. -6. A special script called `digitalocean-synchronize` is installed into - the image. This script is run at every startup to autodetect the network - settings from the metadata service. It also detects if the droplet - was just restored, and if so, it resets the root password and regenerates - the host SSH keys. -7. The image is now ready. The script then generates a "blockplan". It is - essentially a list of instructions to image the virtual disk with the - sparse disk image without requiring any extra space. -8. A minimal root filesystem is generated on RAM so that the disk can - be unmounted. -9. The script calls `systemctl switch-root` to enter the minimal - root filesystem. -10. The disk is unmounted. -11. The blockplan is executed. -12. The bootloader (Grub) is installed. -13. Reboot! -14. Done! diff --git a/digitalocean-synchronize.install b/digitalocean-synchronize.install deleted file mode 100644 index ca8c7b497d9f..000000000000 --- a/digitalocean-synchronize.install +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -post_install() { - systemctl enable digitalocean-synchronize.service -} diff --git a/digitalocean-synchronize.service b/digitalocean-synchronize.service index 74a0c1cd3596..045b8d3a9fa9 100644 --- a/digitalocean-synchronize.service +++ b/digitalocean-synchronize.service @@ -7,6 +7,3 @@ After=systemd-udevd.service [Service] Type=oneshot ExecStart=/usr/sbin/digitalocean-synchronize - -[Install] -WantedBy=multi-user.target diff --git a/digitalocean-synchronize b/digitalocean-synchronize.sh index 35593fa1900f..bf6827baceb5 100644 --- a/digitalocean-synchronize +++ b/digitalocean-synchronize.sh @@ -83,6 +83,12 @@ process_interface() { fi log "Added IPv4 address ${address}/${prefix} on ${interface}." fi + if [[ " ${attrs} " =~ " anchor_ipv4/ " ]]; then + local address=$(curl -sf ${url}anchor_ipv4/address) + local prefix=$(netmask_to_prefix $(curl -sf ${url}anchor_ipv4/netmask)) + echo "Address=${address}/${prefix}" + log "Added Anchor 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) diff --git a/install.sh b/install.sh deleted file mode 100755 index e4b4c3fb1843..000000000000 --- a/install.sh +++ /dev/null @@ -1,802 +0,0 @@ -#!/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" | base64 --decode -} - -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 - - extract_embedded_file digitalocean-synchronize.pkg.tar.xz > ${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 - 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 |