Created
June 15, 2026 13:44
-
-
Save dmmulroy/c1c98c0f14d71f2ccbb22f7036ada5f9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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