haos_vm.sh 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. #!/usr/bin/env bash
  2. NEXTID=$(pvesh get /cluster/nextid)
  3. RELEASE=$(curl -sX GET "https://api.github.com/repos/home-assistant/operating-system/releases" | awk '/tag_name/{print $4;exit}' FS='[""]')
  4. STABLE="7.6"
  5. YW=`echo "\033[33m"`
  6. BL=`echo "\033[36m"`
  7. RD=`echo "\033[01;31m"`
  8. BGN=`echo "\033[4;92m"`
  9. GN=`echo "\033[1;92m"`
  10. DGN=`echo "\033[32m"`
  11. CL=`echo "\033[m"`
  12. BFR="\\r\\033[K"
  13. HOLD="-"
  14. CM="${GN}✓${CL}"
  15. while true; do
  16. read -p "This will create a New Home Assistant OS VM. Proceed(y/n)?" yn
  17. case $yn in
  18. [Yy]* ) break;;
  19. [Nn]* ) exit;;
  20. * ) echo "Please answer yes or no.";;
  21. esac
  22. done
  23. clear
  24. function header_info {
  25. echo -e "${BL}
  26. _ _ ____ _____
  27. | | | | /\ / __ \ / ____|
  28. | |__| | / \ | | | | (___
  29. | __ | / /\ \| | | |\___ \
  30. | | | |/ ____ \ |__| |____) |
  31. |_| |_/_/ ${CL}${YW}v3${CL}${BL} \_\____/|_____/
  32. ${CL}"
  33. }
  34. header_info
  35. function msg_info() {
  36. local msg="$1"
  37. echo -ne " ${HOLD} ${YW}${msg}..."
  38. }
  39. function msg_ok() {
  40. local msg="$1"
  41. echo -e "${BFR} ${CM} ${GN}${msg}${CL}"
  42. }
  43. function default_settings() {
  44. clear
  45. header_info
  46. echo -e "${BL}Using Default Settings${CL}"
  47. echo -e "${DGN}Using Version ${BGN}${STABLE}${CL}"
  48. BRANCH=${STABLE}
  49. echo -e "${DGN}Using VM ID ${BGN}$NEXTID${CL}"
  50. VMID=$NEXTID
  51. echo -e "${DGN}Using VM Name ${BGN}haos${STABLE}${CL}"
  52. VM_NAME=haos${STABLE}
  53. echo -e "${DGN}Using ${BGN}2vCPU${CL}"
  54. CORE_COUNT="2"
  55. echo -e "${DGN}Using ${BGN}4096MiB${CL}"
  56. RAM_SIZE="4096"
  57. echo -e "${DGN}Start VM when completed ${BGN}yes${CL}"
  58. START_VM="yes"
  59. }
  60. function advanced_settings() {
  61. clear
  62. header_info
  63. echo -e "${RD}Using Advanced Settings${CL}"
  64. echo -e "${YW}Type Latest for Version ${RELEASE}, or Press [ENTER] for Stable Version ${STABLE} "
  65. read BRANCH
  66. if [ -z $BRANCH ]; then BRANCH=$STABLE;
  67. else
  68. BRANCH=$RELEASE; fi;
  69. echo -en "${DGN}Set Version To ${BL}$BRANCH${CL}"
  70. echo -e " ${CM}${CL} \r"
  71. sleep 1
  72. clear
  73. header_info
  74. clear
  75. header_info
  76. echo -e "${RD}Using Advanced Settings${CL}"
  77. echo -e "${DGN}Using Version ${BGN}$BRANCH${CL}"
  78. echo -e "${YW}Enter the VM ID, or Press [ENTER] to automatically generate (${NEXTID}) "
  79. read VMID
  80. if [ -z $VMID ]; then VMID=$NEXTID; fi;
  81. echo -en "${DGN}Set VM ID To ${BL}$VMID${CL}"
  82. echo -e " ${CM}${CL} \r"
  83. sleep 1
  84. clear
  85. header_info
  86. echo -e "${RD}Using Advanced Settings${CL}"
  87. echo -e "${DGN}Using Version ${BGN}$BRANCH${CL}"
  88. echo -e "${DGN}Using VM ID ${BGN}$VMID${CL}"
  89. echo -e "${YW}Enter VM Name (no-spaces), or Press [ENTER] for Default: haos${BRANCH} "
  90. read VMNAME
  91. if [ -z $VMNAME ]; then
  92. VM_NAME=haos${BRANCH}
  93. else
  94. VM_NAME=$(echo ${VMNAME,,} | tr -d ' ')
  95. fi
  96. echo -en "${DGN}Set CT Name To ${BL}$VM_NAME${CL}"
  97. echo -e " ${CM}${CL} \r"
  98. sleep 1
  99. clear
  100. header_info
  101. echo -e "${RD}Using Advanced Settings${CL}"
  102. echo -e "${DGN}Using Version ${BGN}$BRANCH${CL}"
  103. echo -e "${DGN}Using VM ID ${BGN}$VMID${CL}"
  104. echo -e "${DGN}Using VM Name ${BGN}$VM_NAME${CL}"
  105. echo -e "${YW}Allocate CPU cores, or Press [ENTER] for Default: 2 "
  106. read CORE_COUNT
  107. if [ -z $CORE_COUNT ]; then CORE_COUNT="2"; fi;
  108. echo -en "${DGN}Set Cores To ${BL}${CORE_COUNT}${CL}"
  109. echo -e " ${CM}${CL} \r"
  110. sleep 1
  111. clear
  112. header_info
  113. echo -e "${RD}Using Advanced Settings${CL}"
  114. echo -e "${DGN}Using Version ${BGN}$BRANCH${CL}"
  115. echo -e "${DGN}Using VM ID ${BGN}$VMID${CL}"
  116. echo -e "${DGN}Using VM Name ${BGN}$VM_NAME${CL}"
  117. echo -e "${DGN}Using ${BGN}${CORE_COUNT}vCPU${CL}"
  118. echo -e "${YW}Allocate RAM in MiB, or Press [ENTER] for Default: 4096 "
  119. read RAM_SIZE
  120. if [ -z $RAM_SIZE ]; then RAM_SIZE="4096"; fi;
  121. echo -en "${DGN}Set RAM To ${BL}$RAM_SIZE${CL}"
  122. echo -e " ${CM}${CL} \n"
  123. sleep 1
  124. clear
  125. header_info
  126. echo -e "${RD}Using Advanced Settings${CL}"
  127. echo -e "${DGN}Using Version ${BGN}$BRANCH${CL}"
  128. echo -e "${DGN}Using VM ID ${BGN}$VMID${CL}"
  129. echo -e "${DGN}Using VM Name ${BGN}$VM_NAME${CL}"
  130. echo -e "${DGN}Using ${BGN}${CORE_COUNT}vCPU${CL}"
  131. echo -e "${DGN}Using ${BGN}${RAM_SIZE}MiB${CL}"
  132. echo -e "${YW}Start VM when completed, or Press [ENTER] for Default: yes "
  133. read START_VM
  134. if [ -z $START_VM ]; then START_VM="yes";
  135. else
  136. START_VM="no"; fi;
  137. echo -en "${DGN}Starting VM when completed ${BL}$START_VM${CL}"
  138. echo -e " ${CM}${CL} \n"
  139. sleep 1
  140. clear
  141. header_info
  142. echo -e "${RD}Using Advanced Settings${CL}"
  143. echo -e "${DGN}Using Version ${BGN}$BRANCH${CL}"
  144. echo -e "${DGN}Using VM ID ${BGN}$VMID${CL}"
  145. echo -e "${DGN}Using VM Name ${BGN}$VM_NAME${CL}"
  146. echo -e "${DGN}Using ${BGN}${CORE_COUNT}vCPU${CL}"
  147. echo -e "${DGN}Using ${BGN}${RAM_SIZE}MiB${CL}"
  148. echo -e "${DGN}Start VM when completed ${BGN}$START_VM${CL}"
  149. read -p "Are these settings correct(y/n)? " -n 1 -r
  150. echo
  151. if [[ ! $REPLY =~ ^[Yy]$ ]]
  152. then
  153. advanced_settings
  154. fi
  155. }
  156. function start_script() {
  157. echo -e "${YW}Type Advanced, or Press [ENTER] for Default Settings "
  158. read SETTINGS
  159. if [ -z $SETTINGS ]; then default_settings;
  160. else
  161. advanced_settings
  162. fi;
  163. }
  164. start_script
  165. set -o errexit
  166. set -o errtrace
  167. set -o nounset
  168. set -o pipefail
  169. shopt -s expand_aliases
  170. alias die='EXIT=$? LINE=$LINENO error_exit'
  171. trap die ERR
  172. trap cleanup EXIT
  173. function error_exit() {
  174. trap - ERR
  175. local DEFAULT='Unknown failure occured.'
  176. local REASON="\e[97m${1:-$DEFAULT}\e[39m"
  177. local FLAG="\e[91m[ERROR] \e[93m$EXIT@$LINE"
  178. msg "$FLAG $REASON"
  179. [ ! -z ${VMID-} ] && cleanup_vmid
  180. exit $EXIT
  181. }
  182. function warn() {
  183. local REASON="\e[97m$1\e[39m"
  184. local FLAG="\e[93m[WARNING]\e[39m"
  185. msg "$FLAG $REASON"
  186. }
  187. function info() {
  188. local REASON="$1"
  189. local FLAG="\e[36m[INFO]\e[39m"
  190. msg "$FLAG $REASON"
  191. }
  192. function msg() {
  193. local TEXT="$1"
  194. echo -e "$TEXT"
  195. }
  196. function cleanup_vmid() {
  197. if $(qm status $VMID &>/dev/null); then
  198. if [ "$(qm status $VMID | awk '{print $2}')" == "running" ]; then
  199. qm stop $VMID
  200. fi
  201. qm destroy $VMID
  202. fi
  203. }
  204. function cleanup() {
  205. popd >/dev/null
  206. rm -rf $TEMP_DIR
  207. }
  208. TEMP_DIR=$(mktemp -d)
  209. pushd $TEMP_DIR >/dev/null
  210. while read -r line; do
  211. TAG=$(echo $line | awk '{print $1}')
  212. TYPE=$(echo $line | awk '{printf "%-10s", $2}')
  213. FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format %.2f | awk '{printf( "%9sB", $6)}')
  214. ITEM=" Type: $TYPE Free: $FREE "
  215. OFFSET=2
  216. if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then
  217. MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET))
  218. fi
  219. STORAGE_MENU+=( "$TAG" "$ITEM" "OFF" )
  220. done < <(pvesm status -content images | awk 'NR>1')
  221. if [ $((${#STORAGE_MENU[@]}/3)) -eq 0 ]; then
  222. warn "'Disk image' needs to be selected for at least one storage location."
  223. die "Unable to detect valid storage location."
  224. elif [ $((${#STORAGE_MENU[@]}/3)) -eq 1 ]; then
  225. STORAGE=${STORAGE_MENU[0]}
  226. else
  227. while [ -z "${STORAGE:+x}" ]; do
  228. STORAGE=$(whiptail --title "Storage Pools" --radiolist \
  229. "Which storage pool you would like to use for the container?\n\n" \
  230. 16 $(($MSG_MAX_LENGTH + 23)) 6 \
  231. "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit
  232. done
  233. fi
  234. msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
  235. msg_ok "Container ID is ${CL}${BL}$VMID${CL}."
  236. msg_info "Getting URL for Home Assistant ${BRANCH} Disk Image"
  237. URL=https://github.com/home-assistant/operating-system/releases/download/${BRANCH}/haos_ova-${BRANCH}.qcow2.xz
  238. msg_ok "Found URL for Home Assistant ${BRANCH} Disk Image"
  239. msg_ok "${CL}${BL}${URL}${CL}"
  240. wget -q --show-progress $URL
  241. echo -en "\e[1A\e[0K"
  242. FILE=$(basename $URL)
  243. msg_ok "Downloaded ${CL}${BL}${BRANCH}.qcow2${CL}${GN} Disk Image"
  244. msg_info "Extracting Disk Image"
  245. unxz $FILE
  246. STORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}')
  247. case $STORAGE_TYPE in
  248. btrfs|nfs|dir)
  249. DISK_EXT=".qcow2"
  250. DISK_REF="$VMID/"
  251. IMPORT_OPT="-format qcow2"
  252. esac
  253. for i in {0,1}; do
  254. disk="DISK$i"
  255. eval DISK${i}=vm-${VMID}-disk-${i}${DISK_EXT:-}
  256. eval DISK${i}_REF=${STORAGE}:${DISK_REF:-}${!disk}
  257. done
  258. msg_ok "Extracted Disk Image"
  259. msg_info "Creating HAOS VM"
  260. qm create $VMID -agent 1 -bios ovmf -cores $CORE_COUNT -memory $RAM_SIZE -name $VM_NAME -net0 virtio,bridge=vmbr0 \
  261. -onboot 1 -ostype l26 -scsihw virtio-scsi-pci
  262. pvesm alloc $STORAGE $VMID $DISK0 128 1>&/dev/null
  263. qm importdisk $VMID ${FILE%.*} $STORAGE ${IMPORT_OPT:-} 1>&/dev/null
  264. qm set $VMID \
  265. -efidisk0 ${DISK0_REF},size=128K \
  266. -scsi0 ${DISK1_REF},size=32G >/dev/null
  267. qm set $VMID \
  268. -boot order=scsi0 >/dev/null
  269. set +o errtrace
  270. (
  271. msg_ok "Created HAOS VM ${CL}${BL}${VM_NAME}"
  272. msg_info "Adding Serial Port and Configuring Console"
  273. trap '
  274. warn "Unable to configure serial port. VM is still functional."
  275. if [ "$(qm config $VMID | sed -n ''/serial0/p'')" != "" ]; then
  276. qm set $VMID --delete serial0 >/dev/null
  277. fi
  278. exit
  279. ' ERR
  280. msg_ok "Added Serial Port and Configured Console"
  281. if [ "$(command -v kpartx)" = "" ]; then
  282. msg_info "Installing kpartx"
  283. apt-get update >/dev/null
  284. apt-get -qqy install kpartx &>/dev/null
  285. msg_ok "Installed kpartx"
  286. fi
  287. DISK1_PATH="$(pvesm path $DISK1_REF)"
  288. DISK1_PART1="$(kpartx -al $DISK1_PATH | awk 'NR==1 {print $1}')"
  289. DISK1_PART1_PATH="/dev/mapper/$DISK1_PART1"
  290. TEMP_MOUNT="${TEMP_DIR}/mnt"
  291. trap '
  292. findmnt $TEMP_MOUNT >/dev/null && umount $TEMP_MOUNT
  293. command -v kpartx >/dev/null && kpartx -d $DISK1_PATH
  294. ' EXIT
  295. kpartx -a $DISK1_PATH
  296. mkdir $TEMP_MOUNT
  297. mount $DISK1_PART1_PATH $TEMP_MOUNT
  298. sed -i 's/$/ console=ttyS0/' ${TEMP_MOUNT}/cmdline.txt
  299. qm set $VMID -serial0 socket >/dev/null
  300. )
  301. if [ "$START_VM" == "yes" ]; then
  302. msg_info "Starting Home Assistant OS VM"
  303. qm start $VMID
  304. msg_ok "Started Home Assistant OS VM"
  305. fi
  306. msg_ok "Completed Successfully!\n"