Предыстория
Имелся у меня машрутизатор на Raspberry Pi4 и на таком-же работал Home Assistant. В какой-то момент понадобился дополнительный UpLink в маршрутезаторе, по что был задействован USB-2-Ethernet адаптер. И всё бы было хорошо, но на ядрах 5.15 и выше, USB сетевой адаптер начал сбрасываться под нагрузкой. Некоторое время повозившись в попытках решить проблему, пришёл к выводу, что пора перетянуть маршрутизатор на что-то с большим количеством и не тратить время на решение проблем USB на RPi. Раз уж решил перевозить маршрутизатор, то и Home Assistant стоило разместить на одном устройстве с маршрутизатором.
Железо
Поиски адекватных вариантов на ARM с количеством независимых Eth портов не увенчался успехом, поэтому остановился на покупке мини пк на Алиэкспрес, вот такой вариант:
CPU: Intel Celeron N5105
Ethernet: 5xi226V 2.5Gb
Memory: 16Gb
SSD: 256Gb
В общем, железо с большим запасом и вполне приемлемой ценой, мне обошлось чуть менее $250 вместе с доставкой.
Установка OpenWRT
С этим всё по уже имеющимся докам, ни каких заморочек не возникло. Использовал образ, собранный самостоятельно при помощи openwrt-imagebuilder-22.03.3-x86-64. Пересобирал, с целью получить требуемые компоненты непосредственно в образе и не заниматься доустановкой ручками. Скрипт для сборки со списком компонентов:
#!/bin/sh # Update imagebuilder from https://downloads.openwrt.org/snapshots/targets/x86/64/ # Be shure, to save /root /home and additional config files from /etc # rpi-eeprom-update -a --- eeprom firmware update if [ -d ../files ]; then [ -d ./files ] && rm -rf ./files cp -a ../files ./ fi # Set CONFIG_TARGET_ROOTFS_PARTSIZE=4096 in .config file KERNEL_PARTSIZE=32 ROOTFS_SIZE=4096 sed -i -re "s/^(CONFIG_TARGET_KERNEL_PARTSIZE=).*$/\\1$KERNEL_PARTSIZE/" .config sed -i -re "s/^(CONFIG_TARGET_ROOTFS_PARTSIZE=).*$/\\1$ROOTFS_SIZE/" .config sed -i -re "s/256/1024/" target/linux/x86/image/Makefile PACKAGES='\ -dnsmasq \ \ kmod-usb-net-rtl8152 kmod-ipt-nat kmod-crypto-sha256 kmod-i2c-core kmod-scsi-generic kmod-scsi-core \ kmod-i2c-smbus kmod-i2c-gpio kmod-mii kmod-usb-net kmod-usb-wdm kmod-usb-net-qmi-wwan \ kmod-usb-net-cdc-mbim kmod-usb-serial-option kmod-usb-serial kmod-usb-serial-wwan kmod-usb-net-cdc-ether kmod-usb-storage \ kmod-usb-xhci-hcd kmod-ipt-nat-extra kmod-inet-diag kmod-bonding kmod-kvm-intel kmod-usb-storage-uas \ kmod-ipt-checksum \ \ screen sudo dmesg htop mc \ usbutils bash zsh owfs openvpn-wolfssl openssh-server \ openssh-client nmap tcpdump umbim ethtool-full uhubctl \ bind-client bind-host bind-nslookup bind-server bind-tools bind-dig \ udpxy zabbix-agentd i2c-tools gcc make ntpd \ nginx-ssl bwm-ng nmap lynx usb-modeswitch uqmi \ procps-ng-sysctl procps-ng-watch procps-ng-uptime procps-ng-top procps-ng-tload procps-ng-ps \ python3 python3-setuptools fdisk iptables-mod-checksum \ lua luasocket luasec luabitop iconv idn \ dnsmasq-full block-mount agetty mwan3 bmon \ qemu-firmware-efi qemu-x86_64-softmmu qemu-bridge-helper qemu-img \ python3-paho-mqtt \ \ luci luci-base luci-nginx luci-ssl-nginx \ luci-proto-qmi luci-proto-3g luci-proto-ipv6 luci-proto-ppp luci-proto-ncm \ luci-theme-bootstrap luci-theme-openwrt-2020 luci-proto-bonding \ \ luci-app-udpxy luci-app-advanced-reboot luci-app-acme luci-app-adblock luci-app-acl \ luci-app-aria2 luci-app-bcp38 luci-app-attendedsysupgrade luci-app-clamav luci-app-commands \ luci-app-transmission luci-app-dcwapd luci-app-ddns luci-app-diag-core \ luci-app-dnscrypt-proxy luci-app-firewall luci-app-frpc luci-app-frps luci-app-fwknopd \ luci-app-hd-idle luci-app-https-dns-proxy luci-app-ksmbd luci-app-lxc luci-app-minidlna \ luci-app-mwan3 luci-app-nextdns luci-app-nft-qos luci-app-nlbwmon luci-app-nut \ luci-app-openvpn luci-app-vnstat luci-app-opkg luci-app-qos luci-app-squid \ luci-app-statistics luci-app-upnp luci-app-mwan3\ \ luci-mod-admin-full luci-mod-network luci-mod-status luci-mod-system luci-mod-rpc \ \ luci-i18n-acl-ru luci-i18n-acme-ru luci-i18n-adblock-ru luci-i18n-advanced-reboot-ru luci-i18n-aria2-ru \ luci-i18n-attendedsysupgrade-ru luci-i18n-base-ru luci-i18n-qos-ru luci-i18n-bcp38-ru luci-i18n-bmx7-ru \ luci-i18n-clamav-ru luci-i18n-commands-ru luci-i18n-dcwapd-ru luci-i18n-ddns-ru \ luci-i18n-diag-core-ru luci-i18n-firewall-ru luci-i18n-frpc-ru luci-i18n-frps-ru luci-i18n-dnscrypt-proxy-ru \ luci-i18n-fwknopd-ru luci-i18n-hd-idle-ru luci-i18n-ksmbd-ru luci-i18n-https-dns-proxy-ru luci-i18n-lxc-ru \ luci-i18n-minidlna-ru luci-i18n-mwan3-ru luci-i18n-nextdns-ru luci-i18n-nft-qos-ru luci-i18n-nlbwmon-ru \ luci-i18n-nut-ru luci-i18n-openvpn-ru luci-i18n-opkg-ru luci-i18n-pbr-ru luci-i18n-mwan3-ru \ ' # -wpad-basic-mbedtls -libustream-mbedtls -libmbedtls -cshark FILES="files/" make -j $(nproc) image PROFILE=generic ADD_LOCAL_KEY=1 BIN_DIR=$(pwd)/../images/ PACKAGES="$PACKAGES" FILES="$FILES"
Собираем образ, заливаем на USB Flash, загружаемся с него и далее заливаем образ на строенный ssd.
Далее, рекомендую создать дополнительный раздел, такого-же размера, как и rootfs. Это упростит дальнейшее обновление OpenWRT, т.к. стандартные механизмы для x86 не работают. Имея доп раздел, можно на него разворачивать новый rootfs, перекидывать конфиги и после этого переключать загрузку на использование второго rootfs. Первый, в это время, будет резервным старой версии. На этом, с OpenWRT всё. Все остальные его настройки у каждого свои :)
Установка Home Assistant
С этим у меня возникли некоторые сложности - предлагаемый вариант OVA образа работает в QEMU, но со сбоями - то перезагрузится, а то и вовсе - зависнет. Естественно, такой вариант для системы автоматизации ни как не годится. Посему, используем haos_generic-x86-64. Я остановился на пер релизе 10.0.rc3. Первым делом нам потребуется увеличить размер самого образа, что бы при первом запуске, автоматически, подстроился размер раздела (умолчательный в районе 6Гб, что маловато). Я свой образ растянул на 48Гб:
dd if=/dev/zero of=haos_generic-x86-64-10.0.rc3.img seek=HAsize bs=4k count=ResultSize
Здесь, HAsize - указываем размер образа в блоках (6442450944/4096=1572864), ResultSize = желаемый размер образа в блоках, в моём случае, для 48Гб: 12582912.
Далее, создаём скрип для запуска образа в qemu. Тут основная сложность в том, что большинство используют его через libvirt, а его нет собранного для OpenWRT, поэтому привожу сразу свой скрипт для запуска/завершения работы вирт.машины. Самое сложное в нём, это корректное выключение виртуальной машины. Располагаем скрипт в /etc/init.d/qemu-ha
#!/bin/sh /etc/rc.common START=99 STOP=1 HA_DIR='/Path_where_your_HAOS_image_stored' IMG='haos_generic-x86-64-10.0.rc3-48Gb.img' QEMU_OPTS="-enable-kvm -cpu host -smp 2 -m 8G \ -hda $HA_DIR/$IMG \ -device e1000,mac=E2:F2:6A:01:9D:C9,netdev=br0 \ -netdev bridge,br=br-lan,id=br0 \ -smbios type=0,uefi=on \ -bios $HA_DIR/OVMF_CODE.fd \ -action shutdown=poweroff \ -qmp tcp:127.0.0.1:4444,server,nowait \ -vnc :0 " qemu_pidfile="/var/run/qemu.pid" start() { /usr/bin/qemu-system-x86_64 $QEMU_OPTS \ -daemonize &> /var/log/qemu.log /usr/bin/pgrep qemu-system-x86_64 > $qemu_pidfile echo "QEMU: Started VM with PID $(cat $qemu_pidfile)." } stop() { echo "QEMU: Sending 'system_powerdown' to VM with PID $(cat $qemu_pidfile)." nc 127.0.0.1 4444 <<QMP { "execute": "qmp_capabilities" } { "execute": "input-send-event", "arguments": { "events": [ { "type": "key", "data" : { "down": true, "key": {"type": "qcode", "data": "power" } } } ] } } QMP if [ "$?" == "0" ]; then if [ -e $qemu_pidfile ]; then if [ -e /proc/$(cat $qemu_pidfile) ]; then echo "QEMU: Waiting for VM shutdown." while [ -e /proc/$(cat $qemu_pidfile) ]; do sleep 1s; done echo "QEMU: VM Process $(cat $qemu_pidfile) finished." else echo "QEMU: Error: No VM with PID $(cat $qemu_pidfile) running." fi rm -f $qemu_pidfile else echo "QEMU: Error: $qemu_pidfile doesn't exist." fi else echo "QEMU: Error: QMP interface can't accept connections or unavailable." fi }
Для автоматического запуска, выполняем: /etc/init.d/qemu-ha enable
Ну и запускаем машину: /etc/init.d/qemu-ha start
Для отладки, к машине можно подключиться по VNC, на пример, используя krdc.
И на последок - скрипт, для мониторинга температуры процессора в HA через mqtt:
#!/usr/bin/env python3 # coding=utf-8 # -*- coding: utf-8 -*- import paho.mqtt.client as mqtt import time import json import os, random pid=os.getpid() with open('/var/run/host_mon_mqtt.pid', "w") as f: f.write(str(pid)) broker_address="192.168.1.4" topic = "Router state" sensor_data = {'temperature_cpu': 0, 'temperature_board': 0} ######################################## #broker_address="iot.eclipse.org" print("Creating new instance") client = mqtt.Client(topic) #create new instance print("connecting to broker") client.connect(broker_address) #connect to broker while not client.is_connected(): client.loop() client.loop_start() #start the loop try: while True: with open('/sys/class/thermal/thermal_zone1/temp', "r") as f: temp1 = f.read().strip() with open('/sys/class/thermal/thermal_zone0/temp', "r") as f: temp2 = f.read().strip() if temp1: temp1 = round(int(temp1)/1000, 2) if temp2: temp2 = round(int(temp2)/1000, 2) if (temp1 != sensor_data['temperature_cpu'] or temp2 != sensor_data['temperature_board']): sensor_data['temperature_cpu'] = temp1 sensor_data['temperature_board'] = temp2 print("Temp is: ", temp1, temp2) client.publish('zigbee2mqtt/'+topic, json.dumps(sensor_data), 1) time.sleep(5) except KeyboardInterrupt: pass client.loop_stop() client.disconnect()
И инит скрипт для него, располагаем /etc/init.d/host_mon_mqtt
#!/bin/sh /etc/rc.common START=99 STOP=1 prog_name="mqtt-temp.py" prog="/mnt/Storage/HomeAssistant/tools/${prog_name}" pidfile="/var/run/host_mon_mqtt.pid" start() { $prog > /var/log/${prog_name}.log 2>&1 & echo "Host monitoring to MQTT: Started with PID $(cat $pidfile)." } stop() { pid=$(cat $pidfile) echo "Host monitoring to MQTT: PID ${pid} shutdown." kill $pid }
Заключение
На данный момент, система работает без сбоев. Запас производительности огромный. Всё стабильно. По завершению обкатки и после первых обновление OpenWRT, постараюсь дополнить.
По факту испытаний, выяснилось, что падения qemu были связаны с ошибками в microcode для kvm на процессорах Intel N5105. Версии микрокода до 0x24000024 (2022-09-02) падают. Лечится очень просто, скачиваем firmware вот тут. В каталог /lib/firmware/intel-ucode и будет стабильно работать.
