#!/bin/bash -eu

if [[ $EUID -ne 0 ]]
then
  echo "STOP: Run sudo -i."
  exit 1
fi

if [ "$(. /etc/os-release && echo "${VERSION_ID:-}")" = "9" ]
then
  echo "STOP: Stretch-based install is no longer supported. Please flash the latest prebuilt."
  exit 1
fi

if [ "${FLOCKED:-}" != "$0" ]
then
  if FLOCKED="$0" flock -en -E 99 "$0" "$0" "$@" || case "$?" in
  99) echo already running
      exit 99
      ;;
  *)  exit $?
      ;;
  esac
  then
    # success
    exit 0
  fi
fi

function setup_progress () {
  local setup_logfile=/teslausb/teslausb-headless-setup.log
  if [ -w $setup_logfile ]
  then
    echo "$( date ) : $*" >> "$setup_logfile"
  fi
  echo "$@"
}

function curlwrapper () {
  setup_progress "curl $*" > /dev/null
  local attempts=0
  while ! curl -s -S --stderr /tmp/curl.err --fail "$@"
  do
    attempts=$((attempts + 1))
    if [ "$attempts" -ge 20 ]
    then
      setup_progress "giving up after 20 tries"
      if [ -e /tmp/curl.err ]
      then
        setup_progress "$(cat /tmp/curl.err)"
      else
        setup_progress '(no reason given)'
      fi
      exit 1
    fi
    setup_progress "'curl $*' failed, retrying" > /dev/null
    sntp -S time.google.com || true
    sleep 3
  done
}

function copy_script () {
  local remote_path="$1"
  local name="${1/*\/}"
  local local_path="$2"

  if [ -z ${SOURCE_DIR:+x} ]
  then
    if declare -F download_sources &> /dev/null
    then
      download_sources
    else
      export SOURCE_DIR=/tmp/sourcedir
      umount "$SOURCE_DIR" &> /dev/null || true
      rm -rf "$SOURCE_DIR"
      mkdir -p "$SOURCE_DIR"
      mount -t tmpfs none "$SOURCE_DIR"
      (
        cd "$SOURCE_DIR"
        curlwrapper -L "https://github.com/${REPO}/teslausb/archive/${BRANCH}.tar.gz" | tar zxf - --strip-components=1
      )
    fi
  fi

  cp "${SOURCE_DIR}/$remote_path" "$local_path/$name"
  chmod +x "$local_path/$name"
  setup_progress "Downloaded $local_path/$name ..."
}

function safesource {
  cat <<EOF > /tmp/checksetupconf
#!/bin/bash -eu
source '$1' &> /tmp/checksetupconf.out
EOF
  chmod +x /tmp/checksetupconf
  if ! /tmp/checksetupconf
  then
    setup_progress "Error in $1:"
    setup_progress "$(cat /tmp/checksetupconf.out)"
    exit 1
  fi
  # shellcheck disable=SC1090
  source "$1"
}

THISDIR=$(dirname "$(readlink -f "$0")")
if [ ! -e "$THISDIR/envsetup.sh" ]
then
  # source the conf file directly to get REPO, BRANCH or download_sources
  # in case they're set
  safesource /root/teslausb_setup_variables.conf
  REPO=${REPO:-marcone}
  BRANCH=${BRANCH:-main-dev}
  copy_script setup/pi/envsetup.sh "$THISDIR"
fi
# SC can't follow non-constant source
# shellcheck disable=SC1090
source "$THISDIR/envsetup.sh"

function dehumanize () {
  echo $(($(echo "$1" | sed 's/GB/G/;s/MB/M/;s/KB/K/;s/G/*1024M/;s/M/*1024K/;s/K/*1024/')))
}

REBOOT=false

# wpa_supplicant should have a country code. Use US as the default
# to avoid using the disallowed channels in the US.
WPA=/etc/wpa_supplicant/wpa_supplicant.conf
if [ -e "$WPA" ] && ! grep -q "country=" $WPA && grep -q "network=" $WPA
then
  setup_progress "adding country code to wpa_supplicant.conf"
  echo "country=US" >> $WPA
  REBOOT=true
fi

BOOT_DISK=$(lsblk -dpno pkname "$(findmnt -D -no SOURCE --target /teslausb)")
declare -rx BOOT_DISK
BOOT_PARTITION_DEVICE=$(lsblk -dpno name "$(findmnt -D -no SOURCE --target /teslausb)")
readonly BOOT_PARTITION_DEVICE
readonly BOOT_DEVICE_PARTITION_PREFIX=${BOOT_PARTITION_DEVICE%?}
export BOOT_DEVICE_PARTITION_PREFIX
ROOT_PARTITION_DEVICE=$(lsblk -dpno name "$(findmnt -D -no SOURCE --target /)")
declare -rx ROOT_PARTITION_DEVICE
readonly ROOT_PART_NUM=${ROOT_PARTITION_DEVICE:0-1}
INCREASE_ROOT_SIZE=$(($(dehumanize "$INCREASE_ROOT_SIZE") / 512))

if [ "$INCREASE_ROOT_SIZE" != "0" ] && [ ! -e "${BOOT_DEVICE_PARTITION_PREFIX}$((ROOT_PART_NUM+1))" ]
then
  if [ ! -e /root/TESLAUSB_ROOT_PARTITION_INCREASED ]
  then
    touch /root/TESLAUSB_ROOT_PARTITION_INCREASED
    ROOTSTART=$(partx --show -g -o START "${ROOT_PARTITION_DEVICE}")
    ROOTSIZE=$(partx --show -g -o SECTORS "${ROOT_PARTITION_DEVICE}")
    ROOTSIZE=$((ROOTSIZE + INCREASE_ROOT_SIZE))
    echo "$ROOTSTART,$ROOTSIZE" | sfdisk --force "${BOOT_DISK}" -N "${ROOT_PART_NUM}"
    setup_progress "increased root partition size"
    REBOOT=true
  else
    setup_progress "increasing root filesystem size to match partition size"
    resize2fs "${ROOT_PARTITION_DEVICE}"
  fi
fi

if [ "$REBOOT" = "true" ]
then
  if [ -t 0 ] && [ "${NO_REBOOT_PROMPT:-}" != "1" ]
  then
    setup_progress "please reboot for changes to take effect"
    exit
  else
    setup_progress "rebooting for changes to take effect"
    reboot
    exit
  fi
fi

function headless_setup_mark_setup_success () {
  rm -f /teslausb/TESLAUSB_SETUP_FAILED
  rm -f /teslausb/TESLAUSB_SETUP_STARTED
  touch /teslausb/TESLAUSB_SETUP_FINISHED
}

function flash () {
  local ON=0
  local OFF=1
  if isPi4 || isPi5
  then
    ON=1
    OFF=0
  fi
  echo none > "$STATUSLED/trigger"
  echo $OFF > "$STATUSLED/brightness"
  sleep 1
  for ((i=1; i<=$1; i++))
  do
    echo $ON > "$STATUSLED/brightness"
    sleep .2
    echo $OFF > "$STATUSLED/brightness"
    sleep .8
  done
}

function headless_setup_progress_flash () {
  if [ ! -t 0 ]
  then
    flash "$1"
  fi
}

function verify_configuration () {
  copy_script setup/pi/verify-configuration.sh /tmp

  /tmp/verify-configuration.sh
}

function get_common_scripts () {
  setup_progress "Downloading common runtime scripts."
  copy_script run/remountfs_rw /root/bin
  copy_script run/make_snapshot.sh /root/bin
  copy_script run/release_snapshot.sh /root/bin
  copy_script run/manage_free_space.sh /root/bin
  copy_script run/force_sync.sh /root/bin
  copy_script run/mountoptsforimage /root/bin
  copy_script run/mountimage /root/bin
  copy_script run/enable_gadget.sh /root/bin
  copy_script run/disable_gadget.sh /root/bin
  ln -sf /root/bin/mountimage /sbin/mount.teslausb
}

# If only the dwc2 module is loaded at boot then the Pi will present
# itself as a non-responsive USB device until a gadget is created.
# When connected to a Windows host, this will show as a device with
# vendor ID 0 (which is invalid), and Windows might pop up an error
# message saying "USB device not recognized", while a connected Linux
# host will log messages like "device descriptor read/64, error -110"
# to the kernel log, and 'lsusb' might hang for over a minute or until
# a gadget is created.
# To work around this, make sure the g_ether module is loaded along
# with dwc2. The archiveloop script will then unload the g_ether
# module (the attached host will see no device at all at this point)
# before creating the mass storage gadget.
function fix_cmdline_txt_modules_load () {
  if [ ! -f "$CMDLINE_PATH" ]
  then
    setup_progress "cmdline.txt does not exist, skipping"
    return
  fi
  # Extract the modules-load portion of the command line.
  # Note that this not handle spaces in the argument.
  local curparam
  curparam=$(grep -P -o " modules.load=[^\s\$]*" "$CMDLINE_PATH" || true)
  if [[ "$curparam" == "" ]]
  then
    curparam="\$"
  elif [[ "$curparam" == *"dwc2"* ]] && [[ "$curparam" == *"g_ether"* ]]
  then
    setup_progress "cmdline.txt is up to date"
    return
  fi

  # replace the current parameter
  setup_progress "Updated cmdline.txt from:"
  setup_progress "$(cat "$CMDLINE_PATH")"
  sed -i "s/${curparam}/ modules-load=dwc2,g_ether/" "$CMDLINE_PATH"
  setup_progress "to:"
  setup_progress "$(cat "$CMDLINE_PATH")"
}

BACKINGFILES_MOUNTPOINT=/backingfiles
MUTABLE_MOUNTPOINT=/mutable

function update_backingfiles_fstab_entry () {
  local filename="$1"
  local mountpoint="$2"
  sed -i "\@^$filename .*@d" /etc/fstab
  if [ -e "$filename" ]
  then
    mkdir -p "$mountpoint"
    echo "$filename $mountpoint teslausb noauto 0 0" >> /etc/fstab
    setup_progress "updated /etc/fstab for $mountpoint"
  fi
}

function mount_with_retry () {
  for i in {1..5}
  do
    if mount "$@"
    then
      return
    fi
    setup_progress "'mount $*' failed, retrying"
    sleep 1
  done
  return 1
}

function create_usb_drive_backing_files () {
  if [ ! -e "$BACKINGFILES_MOUNTPOINT" ]
  then
    mkdir "$BACKINGFILES_MOUNTPOINT"
  fi

  if [ ! -e "$MUTABLE_MOUNTPOINT" ]
  then
    mkdir "$MUTABLE_MOUNTPOINT"
  fi

  copy_script setup/pi/create-backingfiles-partition.sh /tmp
  /tmp/create-backingfiles-partition.sh "$BACKINGFILES_MOUNTPOINT" "$MUTABLE_MOUNTPOINT"

  if ! findmnt --mountpoint $BACKINGFILES_MOUNTPOINT > /dev/null
  then
    setup_progress "Mounting the partition for the backing files..."
    mount $BACKINGFILES_MOUNTPOINT
    setup_progress "Mounted the partition for the backing files."
  fi

  if ! findmnt --mountpoint $MUTABLE_MOUNTPOINT > /dev/null
  then
    setup_progress "Mounting the mutable partition..."
    mount $MUTABLE_MOUNTPOINT
    setup_progress "Mounted the mutable partition."
  fi

  setup_progress "Creating backing disk files."
  copy_script setup/pi/create-backingfiles.sh /tmp
  /tmp/create-backingfiles.sh "$CAM_SIZE" "$MUSIC_SIZE" "$LIGHTSHOW_SIZE" "$BOOMBOX_SIZE" "$BACKINGFILES_MOUNTPOINT" "$USE_EXFAT"

  update_backingfiles_fstab_entry $BACKINGFILES_MOUNTPOINT/cam_disk.bin /mnt/cam
  update_backingfiles_fstab_entry $BACKINGFILES_MOUNTPOINT/music_disk.bin /mnt/music
  update_backingfiles_fstab_entry $BACKINGFILES_MOUNTPOINT/lightshow_disk.bin /mnt/lightshow
  update_backingfiles_fstab_entry $BACKINGFILES_MOUNTPOINT/boombox_disk.bin /mnt/boombox

  # mount cam image and make sure the right directories exist
  umount /mnt/cam > /dev/null || true
  local ret=0
  /root/bin/disable_gadget.sh || ret=$?
  if (( (ret == 0) || (ret == 2) ))
  then
    if [ -e /backingfiles/cam_disk.bin ] && mount_with_retry /mnt/cam
    then
      mkdir -p /mnt/cam/TeslaCam
      mkdir -p /mnt/cam/TeslaTrackMode
      touch /mnt/cam/.metadata_never_index

      local -r sentrylist_previously_archived=/mutable/sentry_files_archived

      # For upgrades from before snapshot-archiving, assume everything from the
      # snapshots -except the files still on the disk image- was already archived,
      # to avoid re-archiving things that were manually deleted from the archive
      # server.
      if [ ! -e "$sentrylist_previously_archived" ] && [ -d "$MUTABLE_MOUNTPOINT/TeslaCam" ]
      then
        find "$MUTABLE_MOUNTPOINT/TeslaCam" -type l -printf '%P\n' | sort > /tmp/allfiles.txt
        find /mnt/cam/TeslaCam /mnt/cam/ -type f -printf '%P\n' | sort > /tmp/stilloncard.txt
        comm -2 -3 /tmp/allfiles.txt /tmp/stilloncard.txt > "$sentrylist_previously_archived"
        rm -f /tmp/allfiles.txt /tmp/stilloncard.txt
      fi

      umount -l /mnt/cam || setup_progress "failed to unmount /mnt/cam"
    fi

    if [ -e /backingfiles/music_disk.bin ] && mount_with_retry /mnt/music
    then
      touch /mnt/music/.metadata_never_index
      umount -l /mnt/music || setup_progress "failed to unmount /mnt/music"
    fi

    if [ -e /backingfiles/lightshow_disk.bin ] && mount_with_retry /mnt/lightshow
    then
      mkdir -p /mnt/lightshow/LightShow
      touch /mnt/lightshow/.metadata_never_index
      umount -l /mnt/lightshow || setup_progress "failed to unmount /mnt/lightshow"
    fi

    if [ -e /backingfiles/boombox_disk.bin ] && mount_with_retry /mnt/boombox
    then
      mkdir -p /mnt/boombox/Boombox
      touch /mnt/boombox/.metadata_never_index
      umount -l /mnt/boombox || setup_progress "failed to unmount /mnt/boombox"
    fi

  else
    setup_progress "STOP: Couldn't check image"
    exit 1
  fi
}

function configure_hostname () {
  local new_host_name="$TESLAUSB_HOSTNAME"
  local old_host_name
  old_host_name=$(hostname)

  # Set the specified hostname if it differs from the current name
  if [ "$new_host_name" != "$old_host_name" ]
  then
    setup_progress "Configuring the hostname..."
    sed -i -e "s/$old_host_name/$new_host_name/g" /etc/hosts
    sed -i -e "s/$old_host_name/$new_host_name/g" /etc/hostname
    while ! hostnamectl set-hostname "$new_host_name"
    do
      if [ ! -e /lib/systemd/system/dbus.service ]
      then
        break
      fi
      setup_progress "hostnamectl failed, retrying"
      sleep 1
    done
    systemctl restart avahi-daemon || true
    setup_progress "Configured hostname: $(hostname)"
  fi
}

function make_root_fs_readonly () {
  copy_script setup/pi/make-root-fs-readonly.sh /tmp
  /tmp/make-root-fs-readonly.sh
}

function update_package_index () {
  setup_progress "Updating package index files..."
  # the package index might be in a bad state if setup was previously
  # interrupted, so fix it up first
  dpkg --configure -a || true
  while ! (apt-get update || apt-get update --allow-releaseinfo-change)
  do
    setup_progress "Failed, retrying"
    sleep 2
  done
}

function upgrade_packages () {
  if [ "$UPGRADE_PACKAGES" = true ]
  then
    setup_progress "Upgrading installed packages..."
    # clean the cache to free up space, since especially
    # a kernel update requires quite a bit of temporary
    # extra space
    apt-get clean
    apt-get --assume-yes upgrade
  else
    setup_progress "Skipping package upgrade."
  fi
  # no real need to keep the cache around after setup
  apt-get clean
  fstrim / || true
}

function set_timezone () {
  if [ -n "${TIME_ZONE:+x}" ]
  then
    if [ -f "/usr/share/zoneinfo/$TIME_ZONE" ]
    then
      ln -sf "/usr/share/zoneinfo/$TIME_ZONE" /etc/localtime
    elif [ "$TIME_ZONE" = "auto" ]
    then
      if curlwrapper -o /root/bin/tzupdate.py https://raw.githubusercontent.com/marcone/tzupdate/develop/tzupdate.py
      then
        chmod +x /root/bin/tzupdate.py
        if ! tzout=$(/root/bin/tzupdate.py 2>&1)
        then
          setup_progress "auto timezone failed: $tzout"
        else
          setup_progress "$tzout"
        fi
      fi
    else
      setup_progress "invalid timezone: $TIME_ZONE"
    fi
  fi
}

# There's probably a better way of doing this.
# If not, there should be.
function get_usb_state {
  local current
  local last;
  current=$(cat /sys/class/udc/*/state)
  echo -n "Gadget state: $current"
  if [ "$current" = "configured" ]
  then
    echo
    return
  fi
  last=$(dmesg | grep 'g_mass_storage\|dwc2' | tail -1)
  if [ -z "$last" ]
  then
    echo ". No UMS/dwc2 messages in dmesg"
    return
  fi
  local usbstatetime
  usbstatetime=$(echo "$last" | tr -d '[]' | awk '{print $1}')
  now=$(awk '{print $1}' /proc/uptime)
  awk "BEGIN {printf \". %.1f seconds ago: \", $now-$usbstatetime}"
  case $last in
    # this message is now a debug log and doesn't show in normal builds
    *"Linux File-Backed Storage")
      echo "connected to host and host mounted drive(s)"
      ;;
    *"new device"*)
      ;&
    *"new address"*)
      echo "connected to host"
      ;;
    *"bound driver"*)
      echo "mass storage ready, but not connected to host (check cable)"
      ;;
    *)
      echo "unknown":
  esac
}

function cmd_install {
  /root/bin/remountfs_rw
  copy_script "$1" /root/bin
  setup_progress "$1 installed in /root/bin/"
  exit
}

function cmd_selfupdate {
  echo "The 'selfupdate' command is no longer supported. Run '$0 upgrade' instead."
  exit 0
}

function cmd_upgrade {
  if [ ! -e /teslausb/TESLAUSB_SETUP_FINISHED ]
  then
    echo "STOP: previous setup didn't finish, can't upgrade unfinished install"
    exit 1
  fi
  copy_script setup/pi/setup-teslausb /tmp &> /dev/null
  copy_script setup/pi/envsetup.sh /tmp &> /dev/null
  exec bash -c "/tmp/setup-teslausb upgrade_prepare && NO_REBOOT_PROMPT=1 /tmp/setup-teslausb && /tmp/setup-teslausb upgrade_finish"
}

function cmd_upgrade_prepare {
  setup_progress "preparing for full upgrade"
  (
    systemctl stop teslausb || true
    killall archiveloop || true
    service smbd stop || true
    service autofs stop || true
    umount /backingfiles/snapshots/snap*/mnt || true
    umount /mnt/cam /mnt/music || true
    umount /mnt/lightshow || true
    umount /mnt/boombox || true
    umount /mnt/archive /mnt/musicarchive || true
    /root/bin/disable_gadget.sh || true
    /root/bin/remountfs_rw
    # a previous bookworm install may have the makers in /boot, so move
    # them to /teslausb (/boot/firmware)
    if [ ! /boot -ef /teslausb ]
    then
      for i in WIFI_ENABLED TESLAUSB_SETUP_STARTED TESLAUSB_SETUP_FINISHED
      do
        if [ -e "/boot/$i" ]
        then
          mv "/boot/$i" /teslausb
        fi
      done
    fi
  ) &> /dev/null
}

function cmd_upgrade_finish {
  mv /tmp/setup-teslausb /root/bin/setup-teslausb
  mv /tmp/envsetup.sh /root/bin/envsetup.sh
  setup_progress "upgrade finished"
  for i in {5..1}
  do
    echo -e -n "rebooting in $i seconds to apply changes, press ctrl-c to abort\r"
    sleep 1
  done
  echo -e '\nRebooting'
  reboot
}

function checkmounted() {
  if ! findmnt "$1" &> /dev/null
  then
    echo "ERROR: $1 is not mounted"
  fi
}

function checkfsrw() {
  local -r opts=$(findmnt -n -o options "$1")
  local -r rw=${opts:0:2}
  if [[ "$rw" != "$2" ]]
  then
    echo "$1 is $rw, should be $2"
  fi
}

function boot_is_not_on_root {
  [ "$(stat -c "%d" /)" -ne "$(stat -L -c "%d" /teslausb)" ]
}

function diagnose {
  local hardware
  local os

  hardware=$( tr -d '\000' < /sys/firmware/devicetree/base/model )
  os=$(. /etc/os-release && echo "$PRETTY_NAME")
  {
    echo -e "====== summary ======"
    echo -e "hardware: ${hardware}"
    echo -e "OS: ${os}"
    if [ "${ARCHIVE_SYSTEM:-unset}" = "unset" ]
    then
      echo "ERROR: no archive method specified!"
    elif [ "${ARCHIVE_SYSTEM:-none}" = "cifs" ]
    then
      if grep -q '/mnt/archive' /etc/fstab
      then
        echo "CIFS archiving selected"
      else
        echo "ERROR: CIFS archiving selected, but archive not defined in fstab"
      fi
    elif [ "${ARCHIVE_SYSTEM:-none}" = "rclone" ]
    then
      if [ ! -e "/root/.config/rclone/rclone.conf" ]
      then
        echo "ERROR: rclone archiving selected, but rclone config does not exist"
      elif [ ! -L "/root/.config/rclone" ]
      then
        echo "ERROR: rclone archiving selected, but rclone config is in /root"
      else
        echo "rclone archiving selected"
      fi
    else
      echo "archive method: ${ARCHIVE_SYSTEM:-unset}"
    fi

    if [[ "$DATA_DRIVE" != "" ]] && (isPi4 || isPi5)
    then
      echo "DATA_DRIVE=$DATA_DRIVE: consider booting from USB instead."
    fi

    if ! blkid -L backingfiles > /dev/null
    then
      echo "ERROR: backingfiles partition does not exist"
    fi
    if [ ! -d /backingfiles ]
    then
      echo "backingfiles directory does not exist"
    fi
    if ! grep -q '/backingfiles' /etc/fstab
    then
      echo "ERROR: backingfiles not in fstab"
    fi

    if [ ! -f /backingfiles/cam_disk.bin ]
    then
      echo "ERROR: cam disk image does not exist"
    fi
    if ! grep -q '/backingfiles/cam_disk.bin' /etc/fstab
    then
      echo "ERROR: cam disk image not in fstab"
    fi
    for i in 0 1 2 3 4 5 6
    do
      LUN="/sys/kernel/config/usb_gadget/teslausb/functions/mass_storage.0/lun.${i}/file"
      if [ -e "$LUN" ]
      then
        echo "lun${i} connected, from file $(cat "$LUN")"
      fi
    done
    if ! blkid -L mutable > /dev/null
    then
      echo "ERROR: mutable partition does not exist"
    fi
    if [ ! -d /mutable ]
    then
      echo "ERROR: mutable directory does not exist"
    fi
    if ! grep -q '/mutable' /etc/fstab
    then
      echo "ERROR: mutable not in fstab"
    fi

    if [ -n "${SNAPSHOT_INTERVAL:+x}" ]
    then
      echo "Snapshot interval: $SNAPSHOT_INTERVAL"
    fi

    numsnapshots=$( mount | grep -c snapshot )
    echo "$numsnapshots snapshots mounted"

    if [ ! -e /teslausb/TESLAUSB_SETUP_FINISHED ]
    then
      echo 'ERROR: setup did not finish'
    fi

    get_usb_state

    local -r archiveloopcount=$(pgrep -f archiveloop | wc -l)
    if ((archiveloopcount <= 2))
    then
      echo "archiveloop is not running"
      journalctl -u teslausb | tail -30
    fi

    local -r inodes=$(df --output=ipcent /mutable/ | sed 1d | sed 's/[^0-9]//g')
    if [[ "$inodes" -gt 80 ]]
    then
      echo "WARNING: /mutable is low on inodes: ${inodes}% used"
    fi

    if [[ "$BRANCH" != "main-dev" || "$REPO" != "marcone" ]]
    then
      echo "WARNING: using $REPO/$BRANCH"
    fi

    if [ -x /root/bin/archive-filter ]
    then
      echo "Custom archive filter is in use"
    fi

    read -r offset <<<"$(sfdisk -l -q -o START /backingfiles/cam_disk.bin | tail -1)"
    fstype=$(blkid --probe -o value -s TYPE --offset $((offset*512)) /backingfiles/cam_disk.bin)
    if [ "$fstype" = "exfat" ]
    then
      echo "CAM drive is formatted as ExFAT. Recommend using FAT32 instead."
    fi
    checkfsrw / ro
    if boot_is_not_on_root
    then
      checkfsrw /teslausb ro
    fi
    checkfsrw /backingfiles rw
    checkfsrw /mutable rw
    checkmounted /var/www/html/TeslaCam
    checkmounted /var/www/html/fs

    echo -e "====== disk / images ======"
    parted -s "${BOOT_DISK}" print || true
    if [ -n "${DATA_DRIVE:+x}" ]
    then
      parted -s "$DATA_DRIVE" print || true
    fi
    if [ -f /backingfiles/cam_disk.bin ]
    then
      echo "cam disk image has $(filefrag /backingfiles/cam_disk.bin | awk '{print $2}') extents"
      parted -s /backingfiles/cam_disk.bin print || true
    else
      echo "no cam disk image found"
    fi
    if [ -f /backingfiles/music_disk.bin ]
    then
      echo "music disk image has $(filefrag /backingfiles/music_disk.bin | awk '{print $2}') extents"
      parted -s /backingfiles/music_disk.bin print || true
    else
      echo "no music disk image found"
    fi
    if [ -f /backingfiles/lightshow_disk.bin ]
    then
      echo "lightshow disk image has $(filefrag /backingfiles/lightshow_disk.bin | awk '{print $2}') extents"
      parted -s /backingfiles/lightshow_disk.bin print || true
    else
      echo "no lightshow disk image found"
    fi
    if [ -f /backingfiles/boombox_disk.bin ]
    then
      echo "boombox disk image has $(filefrag /backingfiles/boombox_disk.bin | awk '{print $2}') extents"
      parted -s /backingfiles/boombox_disk.bin print || true
    else
      echo "no boombox disk image found"
    fi
    df -h /teslausb/ / /backingfiles/ /mutable/
    echo -e "====== network ======"
    ifconfig
    iwconfig wlan0 | grep Link

    echo -e "====== fstab ======"
    if [ -e /etc/fstab ]
    then
      cat /etc/fstab
    else
      echo "no fstab found"
    fi

    echo -e "====== mounts ======"
    mount

    echo -e "====== initial setup boot log ======"
    mkdir /tmp/root$$
    mount --bind / /tmp/root$$
    if [ -e /tmp/root$$/var/log/boot.log ]
    then
      cat /tmp/root$$/var/log/boot.log
    else
      echo "no boot log found"
    fi
    umount /tmp/root$$
    rmdir /tmp/root$$

    echo -e "====== rc.local log ======"
    journalctl -u rc-local

    echo -e "====== setup log ======"
    if [ -e /teslausb/teslausb-headless-setup.log ]
    then
      cat /teslausb/teslausb-headless-setup.log
    else
      echo "no setup log found"
    fi

    echo -e "====== archiveloop log ======"
    if [ -e /mutable/archiveloop.log ]
    then
      cat /mutable/archiveloop.log
    else
      echo "no archiveloop log found"
    fi

    echo -e "====== system log ======"
    if [ -x /bin/logread ]
    then
      /bin/logread
    else
      echo "logread not installed"
    fi

    echo -e "====== dmesg ======"
    dmesg -T
    echo -e "====== process list and uptime ======"
    ps -eaf
    echo "$(hostname) has been $(uptime -p). System time is $(date)"
    echo -e "====== end of diagnostics ======"
  }
}

function cmd_diagnose {
  { diagnose || true; } |&
    # clean up the output a bit
    tr '\r' '\n' |
    sed '/^ *$/d' |
    grep -a -v '^Reading package lists' |
    grep -a -v '^(Reading database' |
    grep -a -v "^Adding 'diversion of" |
    grep -a -v "^Removing 'diversion of" |
    sed -E 's/\o033\[0;32m//' |
    sed -E 's/\o033\[0m//'
}

export -f setup_progress

INSTALL_DIR=${INSTALL_DIR:-/root/bin}
if [ "$INSTALL_DIR" != "/root/bin" ]
then
  setup_progress "WARNING: 'INSTALL_DIR' setup variable no longer supported"
fi

BRANCHNAME="$BRANCH"

if [ -n "${1:+x}" ]
then
  command=cmd_$1
  if typeset -f "$command" > /dev/null
  then
    shift
    $command "$@"
    exit 0
  else
    setup_progress "unknown command: $1"
    exit 1
  fi
fi

# Turn on all leds in case they're still flashing
# from pre-setup.
for led in /sys/class/leds/*
do
  if [ -e "$led/trigger" ]
  then
    echo none > "$led/trigger" || true
    echo 1 > "$led/brightness" || true
  fi
done

# Update config.txt if needed
if [ -f "$PICONFIG_PATH" ]
then
  if ! grep -q 'dtoverlay=dwc2$' "$PICONFIG_PATH"
  then
    echo -e "dtoverlay=dwc2\n" >> "$PICONFIG_PATH"
    setup_progress "rebooting to apply dwc2 overlay change"
    reboot
    exit 0
  fi
fi

configure_hostname

tmpdir=/tmp/$$
mkdir -p "$tmpdir"
copy_script setup/pi/setup-teslausb "$tmpdir"  &> /dev/null
if cmp -s "$tmpdir/setup-teslausb" "$0"
then
  setup_progress "$0 is up to date"
else
  setup_progress "WARNING: $BRANCHNAME contains a different version of $0. It is recommended to run '$0 upgrade' to get the latest version."
fi

copy_script pi-gen-sources/00-teslausb-tweaks/files/rc.local "$tmpdir" &> /dev/null
if cmp -s "$tmpdir/rc.local" /etc/rc.local
then
  setup_progress "rc.local is up to date"
else
  setup_progress "updating rc.local"
  mv "$tmpdir/rc.local" /etc/rc.local
  parent=$(ps -o ppid= $PPID)
  grandparent=$(ps -o ppid= "$((parent))" )
  caller=$(ps -o comm= "$((grandparent))")
  if [ "$caller" = "rc.local" ] && [ ! -t 0 ]
  then
    setup_progress "rebooting to run updated rc.local"
    exec reboot
  fi
fi

update_package_index

# set time zone so we get decent timestamps in the rest of the setup log
set_timezone

# Flash for stage 2 headless (verify requested configuration)
headless_setup_progress_flash 2

setup_progress "Verifying that the requested configuration is valid..."

verify_configuration

# Flash for Stage 3 headless (grab scripts)
headless_setup_progress_flash 3

mkdir -p /root/bin

get_common_scripts

pushd ~

fix_cmdline_txt_modules_load

# Flash for stage 4 headless (Create backing files)
headless_setup_progress_flash 4

create_usb_drive_backing_files

if [ "$CONFIGURE_ARCHIVING" = true ]
then
  setup_progress "calling configure.sh"
  export -f curlwrapper
  export -f copy_script
  copy_script setup/pi/configure.sh /tmp
  /tmp/configure.sh
else
  setup_progress "skipping configure.sh"
fi

if [ "$SAMBA_ENABLED" = "true" ]
then
  export SAMBA_GUEST
  copy_script setup/pi/configure-samba.sh /tmp
  /tmp/configure-samba.sh
fi

if [ -n "${AP_SSID:+x}" ]
then
  copy_script setup/pi/configure-ap.sh /tmp
  /tmp/configure-ap.sh
fi

copy_script setup/pi/configure-automount.sh /tmp
/tmp/configure-automount.sh

copy_script setup/pi/configure-web.sh /tmp
/tmp/configure-web.sh

copy_script setup/pi/configure-ssh.sh /tmp
/tmp/configure-ssh.sh

# source setup-teslausb from .bashrc to set up completion
if ! grep -q envsetup.sh /root/.bashrc
then
  echo "source /root/bin/envsetup.sh" >> /root/.bashrc
fi
sed -i '/source \/root\/bin\/setup-teslausb/d' /root/.bashrc

# UX courtesy reminders
if ! grep -q TESLAUSB_TIP1 /root/.bashrc
then
  cat >> /root/.bashrc <<- EOC
	if [ -n "\$PS1" ]; then
		cat << TESLAUSB_TIP1
		Run 'bin/setup-teslausb upgrade' to update to the latest version of teslausb,
		or run 'bin/remountfs_rw' to allow writing to the root partition.

		TESLAUSB_TIP1
	fi
	EOC
fi

DEFUSER=$(grep ":1000:1000:" /etc/passwd | awk -F : '{print $1}')
if [ -n "$DEFUSER" ]
then
  if ! grep -q TESLAUSB_TIP1 "/home/$DEFUSER/.bashrc"
  then
    cat >> "/home/$DEFUSER/.bashrc" <<- EOC
	if [ -n "\$PS1" ]; then
		cat << TESLAUSB_TIP1
		Run 'sudo -i' if you need to make changes.

		The TeslaUSB web interface is at http://$(hostname -s) or http://$(hostname -I | awk '{print $1}')

		TESLAUSB_TIP1
	fi
	EOC
  fi
  if grep -q "/boot/TESLAUSB_SETUP_FINISHED" "/home/$DEFUSER/.bashrc"
  then
    sed -i "s@/boot/TESLAUSB_SETUP_FINISHED@/teslausb/TESLAUSB_SETUP_FINISHED@" "/home/$DEFUSER/.bashrc"
  fi
  if grep -q "/boot/TESLAUSB_SETUP_FINISHED" "/root/.bashrc"
  then
    sed -i "s@/boot/TESLAUSB_SETUP_FINISHED@/teslausb/TESLAUSB_SETUP_FINISHED@" "/root/.bashrc"
  fi
fi

make_root_fs_readonly

upgrade_packages

if [ -n "${INSTALL_USER_REQUESTED_PACKAGES:-}" ]
then
  setup_progress "Installing user requested packages: ${INSTALL_USER_REQUESTED_PACKAGES}"
  read -r -a EXTRA_PACKAGES <<< "${INSTALL_USER_REQUESTED_PACKAGES[@]}"
  DEBIAN_FRONTEND=noninteractive apt-get -y --force-yes install "${EXTRA_PACKAGES[@]}"
fi

headless_setup_mark_setup_success

# Flash for stage 5 headless (Mark success, FS readonly)
headless_setup_progress_flash 5

setup_progress "All done."

if [ -t 0 ] && [ "${NO_REBOOT_PROMPT:-}" != "1" ]
then
  setup_progress '(reboot now for changes to take effect)'
fi
