Skip to content

Instantly share code, notes, and snippets.

@dmmulroy
Created June 15, 2026 13:44
Show Gist options
  • Select an option

  • Save dmmulroy/c1c98c0f14d71f2ccbb22f7036ada5f9 to your computer and use it in GitHub Desktop.

Select an option

Save dmmulroy/c1c98c0f14d71f2ccbb22f7036ada5f9 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
set -euo pipefail
# Writes Home Assistant OS to this machine's internal disk.
# Intended to be run from a live Ubuntu USB session, not from the OS installed
# on the internal drive.
DEFAULT_IMAGE="$HOME/Downloads/haos_generic-x86-64-17.3.img.xz"
DEFAULT_TARGET="/dev/nvme0n1"
IMAGE="${1:-$DEFAULT_IMAGE}"
TARGET="${2:-$DEFAULT_TARGET}"
die() {
echo "ERROR: $*" >&2
exit 1
}
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"
}
parent_disk() {
local dev="$1"
local pk
pk="$(lsblk -no PKNAME "$dev" 2>/dev/null | head -n1 || true)"
if [[ -n "$pk" ]]; then
printf '/dev/%s\n' "$pk"
return
fi
# If this is already a whole disk, lsblk PKNAME is empty.
if [[ -b "$dev" ]]; then
printf '%s\n' "$dev"
fi
}
need_cmd lsblk
need_cmd findmnt
need_cmd xzcat
need_cmd dd
need_cmd sync
[[ -f "$IMAGE" ]] || die "HAOS image not found: $IMAGE"
[[ "$IMAGE" == *.img.xz ]] || die "Expected a compressed .img.xz HAOS image: $IMAGE"
[[ -b "$TARGET" ]] || die "Target block device not found: $TARGET"
target_type="$(lsblk -dnro TYPE "$TARGET")"
[[ "$target_type" == "disk" ]] || die "Target must be a whole disk, not a partition: $TARGET"
root_source="$(findmnt -nro SOURCE / || true)"
root_disk=""
if [[ -n "$root_source" && "$root_source" == /dev/* ]]; then
root_disk="$(parent_disk "$root_source")"
fi
if [[ -n "$root_disk" && "$root_disk" == "$TARGET" ]]; then
die "$TARGET appears to contain the currently running root filesystem. Boot from the Ubuntu live USB and run this script there."
fi
echo
echo "Home Assistant OS disk writer"
echo
echo "Image: $IMAGE"
echo "Target: $TARGET"
echo
echo "Current disk layout:"
lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL,MOUNTPOINTS,MODEL,TRAN
echo
echo "This will ERASE ALL DATA on $TARGET and replace it with Home Assistant OS."
echo "The internal Pop!_OS install and all files on that disk will be destroyed."
echo
read -r -p "Type WRITE HAOS to continue: " confirm
[[ "$confirm" == "WRITE HAOS" ]] || die "Confirmation did not match; aborting."
echo
echo "Unmounting any mounted partitions on $TARGET..."
while read -r mountpoint; do
[[ -n "$mountpoint" ]] || continue
sudo umount "$mountpoint"
done < <(lsblk -nrpo MOUNTPOINTS "$TARGET" | sed '/^$/d')
echo
echo "Writing HAOS image to $TARGET..."
echo "Do not power off or unplug anything until this finishes."
xzcat "$IMAGE" | sudo dd of="$TARGET" bs=4M status=progress conv=fsync
echo
echo "Flushing remaining writes..."
sync
echo
echo "Final disk layout:"
lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL,MOUNTPOINTS,MODEL,TRAN "$TARGET" || lsblk -o NAME,SIZE,TYPE,FSTYPE,LABEL,MOUNTPOINTS,MODEL,TRAN
echo
echo "Done. Shut down, remove the Ubuntu USB, and boot from the internal drive."
echo "Home Assistant should become available at: http://homeassistant.local:8123"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment