Esta guía describe cómo crear y gestionar VMs de Home Assistant OS en Proxmox VE, incluyendo el script de creación automática y la gestión de red.
DNS estándar del proyecto: 8.8.8.8 como primario y 8.8.4.4 como secundario. Usa el mismo criterio en Proxmox, OpenWRT y HAOS para evitar discrepancias durante el arranque inicial.
Internet (GCP)
│
ens4 (IP dinámica GCP)
│
Proxmox Host — NAT/MASQUERADE
├── vmbr0 (192.168.100.1/24) — red interna Proxmox
│ │
│ OpenWRT VM (VMID 200)
│ ├── eth0 → WAN → 192.168.100.2/24
│ └── br-lan → LAN → 10.10.10.2/24
│
└── vmbr1 (10.10.10.1/24) — red de clientes
│
VMs HAOS → IP 10.10.10.<VMID>
Regla de IPs: cada VM recibe la IP 10.10.10.<VMID>. El VMID debe estar entre 1 y 254.
vmbr0 creado como Linux bridge sin puertos físicosvmbr1 creado como Linux bridge con IP 10.10.10.1/24 en el hostcurl, jq, wget, xz, python3Un único script interactivo que descarga HAOS, crea la VM, verifica la configuración de discos y configura la red al primer arranque. Si algo falla durante la creación, destruye la VM automáticamente para dejar el entorno limpio.
cat > /usr/local/bin/HA-vm.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
log() { echo "[INFO] $*"; }
err() { echo "[ERROR] $*" >&2; exit 1; }
warn() { echo "[WARN] $*"; }
ok() { echo "[OK] $*"; }
[[ "$EUID" -ne 0 ]] && err "Ejecuta como root"
for cmd in curl jq wget xz pvesm qm python3; do
command -v "$cmd" >/dev/null 2>&1 || err "Dependencia no encontrada: $cmd"
done
# ── Versión ──────────────────────────────────────────────────
log "Consultando última versión de HAOS..."
VER=$(curl -sf "https://api.github.com/repos/home-assistant/operating-system/releases/latest" \
| jq -r '.tag_name' | sed 's/^v//')
[[ -z "$VER" || "$VER" == "null" ]] && err "No se pudo obtener la versión de HAOS."
log "Versión: $VER"
# ── Parámetros ───────────────────────────────────────────────
echo ""
echo "=== Crear VM de Home Assistant OS ==="
echo ""
pvesm status | grep -E '^(local|data)\s'
echo ""
read -rp "Storage para la VM [data]: " STORAGE
STORAGE="${STORAGE:-data}"
pvesm status | grep -qE "^${STORAGE}\s" || err "Storage '${STORAGE}' no encontrado."
echo ""
while true; do
read -rp "VMID (1–254): " VMID
[[ "$VMID" =~ ^[0-9]+$ ]] || { warn "VMID debe ser numérico."; continue; }
[[ "$VMID" -ge 1 && "$VMID" -le 254 ]] || { warn "VMID debe estar entre 1 y 254."; continue; }
qm status "$VMID" &>/dev/null && { warn "VMID $VMID ya existe."; continue; }
break
done
read -rp "Nombre de la VM: " NAME
[[ -z "$NAME" ]] && err "El nombre no puede estar vacío."
NAME="${NAME//[ _]/-}"
HA_IP="10.10.10.${VMID}"
HA_GW="10.10.10.2"
HA_DNS="8.8.8.8"
log "IP que se asignará: ${HA_IP}"
# ── Descarga ─────────────────────────────────────────────────
IMG_XZ="/tmp/haos_${VER}.qcow2.xz"
IMG_FILE="/tmp/haos_${VER}.qcow2"
IMG_URL="https://github.com/home-assistant/operating-system/releases/download/${VER}/haos_ova-${VER}.qcow2.xz"
VMID_CREATED=0
cleanup() {
rm -f "$IMG_XZ" "$IMG_FILE"
if [[ "$VMID_CREATED" -eq 1 ]]; then
warn "Error durante la creación — destruyendo VM $VMID."
qm stop "$VMID" &>/dev/null || true
qm destroy "$VMID" --purge &>/dev/null || true
fi
}
trap cleanup EXIT
log "Descargando HAOS ${VER}..."
wget -q --show-progress -O "$IMG_XZ" "$IMG_URL" || err "Fallo al descargar la imagen."
log "Descomprimiendo..."
xz -d "$IMG_XZ"
[[ -f "$IMG_FILE" ]] || err "La imagen no se descomprimió correctamente."
ok "Imagen lista."
# ── Crear VM ─────────────────────────────────────────────────
log "Creando VM $VMID ($NAME)..."
qm create "$VMID" \
--name "$NAME" \
--memory 4096 \
--cores 2 \
--net0 virtio,bridge=vmbr1 \
--scsihw virtio-scsi-pci \
--bios ovmf \
--machine q35 \
--serial0 socket \
--vga serial0 \
--agent enabled=1 \
--tablet 0
VMID_CREATED=1
log "Añadiendo disco EFI (Secure Boot OFF)..."
qm set "$VMID" --efidisk0 "${STORAGE}:4,efitype=4m,pre-enrolled-keys=0"
log "Importando imagen HAOS en '${STORAGE}'..."
qm importdisk "$VMID" "$IMG_FILE" "$STORAGE"
rm -f "$IMG_FILE"
# Verificar que hay exactamente 1 disco sin adjuntar
UNUSED_COUNT=$(qm config "$VMID" | grep -cE '^unused[0-9]+: ' || true)
[[ "$UNUSED_COUNT" -eq 1 ]] || err "Se esperaba 1 disco importado sin adjuntar, se encontraron ${UNUSED_COUNT}."
DISK_VOLUME=$(qm config "$VMID" | awk -F': ' '/^unused0: /{print $2; exit}')
[[ -n "$DISK_VOLUME" ]] || err "No se encontró el disco importado (unused0)."
log "Adjuntando disco HAOS: $DISK_VOLUME"
qm set "$VMID" \
--scsi0 "${DISK_VOLUME},discard=on,ssd=1" \
--boot order=scsi0
# Verificar que no quedan discos sin adjuntar
REMAINING=$(qm config "$VMID" | grep -cE '^unused[0-9]+: ' || true)
[[ "$REMAINING" -eq 0 ]] || err "Quedan $REMAINING disco(s) sin adjuntar — revisar configuración."
ok "Discos:"
qm config "$VMID" | grep -E '^(efidisk|scsi|boot)'
# ── Arrancar ─────────────────────────────────────────────────
log "Arrancando VM $VMID..."
qm start "$VMID"
# ── Red ──────────────────────────────────────────────────────
log "Esperando QEMU guest agent (hasta 10 min — primer arranque resiza particiones)..."
TIMEOUT=600
ELAPSED=0
until qm guest cmd "$VMID" ping &>/dev/null; do
sleep 5
ELAPSED=$((ELAPSED + 5))
[[ "$ELAPSED" -lt "$TIMEOUT" ]] || err "Timeout esperando el guest agent. Ejecuta HA-network.sh $VMID cuando la VM esté lista."
log " ... ${ELAPSED}s / ${TIMEOUT}s"
done
ok "Guest agent disponible."
log "Detectando interfaz de red..."
HA_IFACE=$(qm guest exec "$VMID" -- ha network info 2>/dev/null | python3 -c "
import json, sys
try:
outer = json.load(sys.stdin)
inner = json.loads(outer.get('out-data', '{}'))
ifaces = inner.get('data', {}).get('interfaces', [])
if isinstance(ifaces, list):
for i in ifaces:
if isinstance(i, dict) and i.get('type') == 'ethernet':
print(i.get('interface', '')); sys.exit(0)
elif isinstance(ifaces, dict):
for name, i in ifaces.items():
if isinstance(i, dict) and i.get('type') == 'ethernet':
print(name); sys.exit(0)
except Exception:
pass
" 2>/dev/null || true)
if [[ -z "$HA_IFACE" ]]; then
warn "ha network info sin resultado, probando ip link..."
HA_IFACE=$(qm guest exec "$VMID" -- ip -o link show 2>/dev/null | python3 -c "
import json, sys
try:
outer = json.load(sys.stdin)
for line in outer.get('out-data', '').splitlines():
parts = line.split()
if len(parts) >= 2:
name = parts[1].rstrip(':')
if name != 'lo':
print(name); sys.exit(0)
except Exception:
pass
" 2>/dev/null || true)
fi
[[ -n "$HA_IFACE" ]] || { warn "Interfaz no detectada, usando enp6s18"; HA_IFACE="enp6s18"; }
ok "Interfaz: $HA_IFACE"
log "Aplicando red: ${HA_IFACE} → ${HA_IP}/24 gw ${HA_GW} dns ${HA_DNS}"
RESULT=$(qm guest exec "$VMID" -- \
ha network update "$HA_IFACE" \
--ipv4-method static \
--ipv4-address "${HA_IP}/24" \
--ipv4-gateway "$HA_GW" \
--ipv4-nameserver "$HA_DNS" 2>&1)
EXITCODE=$(echo "$RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('exitcode',1))" 2>/dev/null || echo 1)
[[ "$EXITCODE" -eq 0 ]] || err "ha network update falló (exitcode $EXITCODE). Salida: $RESULT"
trap - EXIT
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " VM creada y configurada"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " VMID : $VMID"
echo " Nombre : $NAME"
echo " Versión HAOS: $VER"
echo " Storage : $STORAGE"
echo " Interfaz : $HA_IFACE"
echo " IP : $HA_IP"
echo " Gateway : $HA_GW"
echo " DNS : $HA_DNS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
EOF
chmod +x /usr/local/bin/HA-vm.sh
bash /usr/local/bin/HA-vm.sh
Pide storage, VMID y nombre. La IP se asigna como 10.10.10.<VMID>. Si algo falla la VM se destruye sola para no dejar basura.
Útil si la VM ya existe y arrancada pero no tiene IP — por ejemplo tras un timeout del primer arranque, o para reaplicar la configuración de red en cualquier momento.
cat > /usr/local/bin/HA-network.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
log() { echo "[INFO] $*"; }
err() { echo "[ERROR] $*" >&2; exit 1; }
warn() { echo "[WARN] $*"; }
ok() { echo "[OK] $*"; }
[[ "$EUID" -ne 0 ]] && err "Ejecuta como root"
VMID="${1:-}"
[[ -n "$VMID" ]] || err "Uso: HA-network.sh <VMID>"
HA_IP="10.10.10.${VMID}"
HA_GW="10.10.10.2"
HA_DNS="8.8.8.8"
qm status "$VMID" | grep -q "status: running" || err "La VM $VMID no está arrancada."
log "Esperando QEMU guest agent en VM $VMID (hasta 10 min)..."
TIMEOUT=600
ELAPSED=0
until qm guest cmd "$VMID" ping &>/dev/null; do
sleep 5
ELAPSED=$((ELAPSED + 5))
[[ "$ELAPSED" -lt "$TIMEOUT" ]] || err "Timeout esperando el guest agent de la VM $VMID."
log " ... ${ELAPSED}s / ${TIMEOUT}s"
done
ok "Guest agent disponible."
log "Detectando interfaz de red..."
HA_IFACE=$(qm guest exec "$VMID" -- ha network info 2>/dev/null | python3 -c "
import json, sys
try:
outer = json.load(sys.stdin)
inner = json.loads(outer.get('out-data', '{}'))
ifaces = inner.get('data', {}).get('interfaces', [])
if isinstance(ifaces, list):
for i in ifaces:
if isinstance(i, dict) and i.get('type') == 'ethernet':
print(i.get('interface', '')); sys.exit(0)
elif isinstance(ifaces, dict):
for name, i in ifaces.items():
if isinstance(i, dict) and i.get('type') == 'ethernet':
print(name); sys.exit(0)
except Exception:
pass
" 2>/dev/null || true)
if [[ -z "$HA_IFACE" ]]; then
warn "ha network info sin resultado, probando ip link..."
HA_IFACE=$(qm guest exec "$VMID" -- ip -o link show 2>/dev/null | python3 -c "
import json, sys
try:
outer = json.load(sys.stdin)
for line in outer.get('out-data', '').splitlines():
parts = line.split()
if len(parts) >= 2:
name = parts[1].rstrip(':')
if name != 'lo':
print(name); sys.exit(0)
except Exception:
pass
" 2>/dev/null || true)
fi
[[ -n "$HA_IFACE" ]] || { warn "Interfaz no detectada, usando enp6s18"; HA_IFACE="enp6s18"; }
ok "Interfaz: $HA_IFACE"
log "Aplicando red: ${HA_IFACE} → ${HA_IP}/24 gw ${HA_GW} dns ${HA_DNS}"
RESULT=$(qm guest exec "$VMID" -- \
ha network update "$HA_IFACE" \
--ipv4-method static \
--ipv4-address "${HA_IP}/24" \
--ipv4-gateway "$HA_GW" \
--ipv4-nameserver "$HA_DNS" 2>&1)
EXITCODE=$(echo "$RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('exitcode',1))" 2>/dev/null || echo 1)
[[ "$EXITCODE" -eq 0 ]] || err "ha network update falló (exitcode $EXITCODE). Salida: $RESULT"
ok "Red configurada: ${HA_IP}/24 en VM $VMID."
EOF
chmod +x /usr/local/bin/HA-network.sh
bash /usr/local/bin/HA-network.sh <VMID>
Si HAOS se queda en Preparando Home AssistantHA-network.sh <VMID> desde el host Proxmox. La interfaz se detecta automáticamente; el gateway es siempre 10.10.10.2 y el DN
S 8.8.8.8.
Si la VM arranca pero no responde al ping ni aparece en la red, verifica:
scsi0 y un efidisk0. Si hay discos unused0, elimínalos antes de arrancar..qcow2, no .raw. Usa qm importdisk con --format qcow2.Comandos de diagnóstico:
# Ver discos de la VM
qm config <VMID> | grep -E '(efidisk|scsi|unused)'
# Verificar que el guest agent responde
qm guest cmd <VMID> ping
# Verificar que el supervisor HAOS esté listo
qm guest exec <VMID> -- ha supervisor info
# Aplicar red manualmente (si el script falló)
bash /usr/local/bin/HA-network.sh <VMID>
El script actualizado detecta la interfaz dinámicamente y espera hasta 10 minutos al guest agent + 10 minutos al supervisor HAOS.
Si HAOS se queda en Preparando Home Assistant o no responde al ping, ejecuta HA-network.sh <VMID> desde el host Proxmox. La interfaz se detecta automáticamente; el gateway es siempre 10.10.10.2 y el DNS 8.8.8.8.
Si la VM falla al arrancar con BdsDxe: Access Denied, el problema es Secure Boot activo. Solución:
qm set <VMID> --delete efidisk0
qm set <VMID> --efidisk0 <storage>:4,efitype=4m,pre-enrolled-keys=0
Para crear un contenedor Debian desde línea de comandos:
pct create <ID> data:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst \
--hostname <nombre> \
--cores 1 \
--memory 512 \
--swap 512 \
--rootfs data:20 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.100.X/24,gw=192.168.100.1 \
--net1 name=eth1,bridge=vmbr1,ip=10.10.10.X/24 \
--nameserver 8.8.8.8 \
--unprivileged 1 \
--password <contraseña> \
--start 1
Usar siempre data como almacenamiento, nunca local o local-lvm.
Por defecto Debian 13 no permite login de root por contraseña. Editar /etc/ssh/sshd_config y descomentar o modificar:
PermitRootLogin yes
PasswordAuthentication yes
Reiniciar SSH:
systemctl restart ssh
Ver también: OpenWRT en Proxmox, Configuración de Proxmox, Troubleshooting Proxmox