Home server (Ubuntu Server, KVM, Home Assistant OS)¶
- Home server (Ubuntu Server, KVM, Home Assistant OS)
- 1. Ubuntu Server 26.04
- 2. Home Assistant
- Virtualization
- Install KVM (Kernel-based Virtual Machine) and libvirt
- Home Assistant OS in a VM
- 2. Accessing HAOS from outside your local network — Cloudflare Tunnel Setup
- 1️⃣ Basic Information (Top Section)
- 2️⃣ Add Public Hostname (IMPORTANT)
- 3️⃣ Browser Rendering Settings
- 4️⃣ Policies (MOST IMPORTANT PART)
- 5️⃣ (Optional) Add a Second Policy – Emergency Lockdown
- 6️⃣ Login Methods
- Go back to the previous page to see the policies
- 7️⃣ Save the Application ✅
- Activate MFA
- Home Assistant devices, add-ons, and integrations
- Apps and Add-ons
- Integrations
- Dashboard and Cards
- Automations, Scenes, Script, Helpers and Entities
- AdGuard Home address naming resolution using the router (Freebox)
- todo:
1. Ubuntu Server 26.04¶
Before everything. If this server was your DHCP Server, activate temporarily the DHCP of your router so the machine can get an IP address.
Setup¶
- Set up an Ubuntu Server on a laptop.
- You better have Ethernet connection. If you only have Wi-Fi, buy a usb Ethernet adapter. It's worth it, and you will be able to use the bridge mode which is not available with wifi.
- boot up ubuntu from an usb drive, select the language, keyboard layout, and timezone. In the network section, select the Ethernet connection, an IP 192.168.x.x will be assigned.
- Wait for mirror to be configured.
- In storage configuration, select Entire disk with 'encrypt the LVM group with LUKS'. Since we will install HAOS inside a VM later, create a partition for
/var/lib/libvirt/images. - Install
openssh-serverand manage it remotely using SSH from another machine so you can copy-paste commands from the documentation. - enable ssh auto start :
sudo systemctl enable sshandsudo systemctl start ssh
First checks and updates¶
- Run updates:
sudo apt update && sudo apt upgrade
# if you face an error with CDROM
sudo rm /etc/apt/sources.list.d/cdrom.sources
sudo apt autoremove -y
- Run
ip addrto check the IP address. This is your HOST-IP. It looks like192.168.x.x( you will have a IPV4 and IPV6 address) - Run
ping google.comto check the connection. - If this machine was your DHCP server. You need to have a static IP, and point to the dns and router gateway (
sudo vim /etc/netplan/01-net.yamlthen setdhcp4: noand add address, nameserver and dns) - Connect remotely in the same network using SSH and the server IP from another machine that has a GUI and a browser so you can copy-paste commands from the documentation. You can either use the IPV6 or the IPV4of the server. This is because we don't have a DHCP server and since the machine is NOT using DHCP right now (dhcp4:no), the router is NOT giving your server: IP Gateway DNS. And we need to define it manually until we add the DHCP server.
-
SSH authentication: SSH always requires login, even on your own network. With a
password, you are prompted to type it on every connection. With an SSH key (ssh-keygen -t ed25519 -a 100, thenssh-copy-id [email protected]), no prompt appears — the key proves your identity automatically (your client may ask for the key's passphrase once, or ssh-agent remembers it). If both are set, the key is tried first and the password acts as a fallback. Note: on iOS, the Files app supports SMB only (no SFTP) — for SFTP/SSH use Termius (or another SSH/SFTP client) instead. -
I customized the shell environment by installing Zsh and tools from the Personal OS Setup to improve usability and productivity.
Lid closed (Laptop)¶
- File:
sudo vim /etc/systemd/logind.conf - Change this line:
HandleLidSwitch=ignore(uncomment it too) - Restart with
sudo systemctl restart systemd-logind
2. Home Assistant¶
Virtualization¶
- I verified that virtualization was supported on the system, checked Secure Boot status:
- Run:
Should return a number > 0.
- Sometimes it returns
0. In that case you need to check if Virtualization is enabled in your BIOS to ensure compatibility. - If you still have
0, and you are sure that Virtualization is enabled in your BIOS, ignore the0.
Install KVM (Kernel-based Virtual Machine) and libvirt¶
- Install:
sudo apt-get install qemu-system-x86 libvirt-daemon-system virtinst \
ovmf swtpm qemu-utils guestfs-tools libosinfo-bin -y
- Verify:
- Check libvirt:
Restart the terminal and run:
If it is the case, you can run virsh without sudo.
Create a bridge configuration¶
A network bridge in KVM allows your virtual machines to appear directly on the same network as your host.
-
Without a bridge: VMs often use NAT (Network Address Translation). They can access the internet, but other devices on your LAN cannot access the VMs directly.
-
With a bridge: VMs act like physical machines on your network. They get an IP from your router (or DHCP server) just like your host, and other devices can communicate with them directly.
-
This works only with Ethernet, not with Wi-Fi.
Important: The bridge will create new Mac Addresses. So if you had a DHCP with devices that used static IP through the bridge, you will need to update it. For adguard, you will need to go to DHCP settings, delete the old client and create a new one in DHCP leases after it detects the new IP address.
How to:
- Identify your network interface with
ip addr, it should look likeenp0s31f6orenx001It is where you can see eth0. - Create a bridge configuration file in
/etc/netplan/with the following content: - Create a bridge configuration file with
sudo vim /etc/netplan/01-bridge.yaml, replaceenx001cc253c478with your interface name
network:
version: 2
renderer: networkd
ethernets:
enx001cc253c478:
dhcp4: no
bridges:
br0:
interfaces: [enx001cc253c478]
dhcp4: yes
parameters:
stp: false
forward-delay: 0
sudo chmod 600 /etc/netplan/*.yaml- Before applying the configuration with
sudo netplan apply, connect physically to the server because the next command will change the ip address of the SSH and your SSH connection will be lost. Or check the IP address of the server in the router. - Your host’s IP will move from enx001cc253c478 to br0
- Apply the configuration:
If you are connected via SSH, you will probably get disconnected unless the static ip is set. Go physically to the server (or check the devices in the router) and get the new IP address:
Your interface name will no longer have a direct IP. All traffic goes through br0.
To connect to the server via SSH, you will need to use the br0 address.
Try ping 8.8.8.8
Home Assistant OS in a VM¶
-
Doc: https://www.home-assistant.io/installation/alternative#virtual-machines
-
Download the Home Assistant OS image:
wget https://github.com/home-assistant/operating-system/releases/download/16.3/haos_ova-16.3.qcow2.xz
- Move the image to the libvirt directory:
- Uncompress the image:
Important: we will use sudo so with all the commands later like
virsh list --allshould use sudosudo virsh list --allotherwise it will show nothing.
- HAOS runs under system libvirt and needs to be managed by root and uses /var/lib/libvirt *
| Feature | system (root) |
user | Notes / Explanation |
|---|---|---|---|
| Autostart on boot | ✅ | ⚠️ Possible | Root/systemd can autostart VMs easily. Users can use systemd --user or autostart scripts, but extra setup required. |
| USB passthrough | ✅ | ⚠️ Possible | Root can attach any USB device. Users can do it if they’re in libvirt/kvm groups and udev rules allow access. |
| Bluetooth / Zigbee | ✅ | ⚠️ Possible | Needs hardware access; root can do it directly. Users need correct groups and permissions. |
| Headless server use | ✅ | ✅ | Works for both. Headless VM does not require GUI. |
| Network bridges | ✅ | ⚠️ Possible | Root can create bridges freely. Users need pre-made bridges or sudo for creation. |
| IP forwarding / port mapping | ✅ | ❌ | Modifying sysctl and iptables requires root. Users cannot forward ports without sudo. |
| Cloudflare tunnel stability | ✅ | ✅ | Independent of root vs user. Tunnels are stable as long as service runs correctly. |
- Create the VM: ( we are adding --network bridge=br0) to use the bridge. If the DHCP server is down, the VM will not get an IP address and will have a lot of problems. Enable temporarily the DHCP server of your router.
sudo virt-install --name haos --description "Home Assistant OS" --os-variant=generic --ram=4096 --vcpus=2 --disk /var/lib/libvirt/images/haos_ova-16.3.qcow2,bus=scsi --controller type=scsi,model=virtio-scsi --import --graphics none --boot uefi --network bridge=br0,model=virtio --console pty,target_type=serial
-
Once you see
Welcome to Home Assistant homeassistant login:, you can quit leave the console usingCtrl + Alt Gr +]or Open a new terminal. -
Now we need to check if HAOS is running:
sudo virsh list --all, you should see the VM running namedhaos- Find the IP address of the HAOS VM: From your router’s DHCP table (recommended) on your router app/website. As you can understand, the VM is like a device that is connected to the router, that is why the router assigned it an IP address.
- The default port is
8123 - Check if HAOS is running : open a browser and go to
http://<HAOS-IP>:8123or use curl:curl -v http://<HAOS-IP>:8123 - Go to
http://homeassistant.local:8123/. This is the default address if the name ishomeassistantin Settings -> System -> Network -> Hostname :homeassistant. If the name is something else like 'ha' the address will behttp://ha.local:8123/.
- The default port is
- You should see the Home Assistant login page.
- If you have a backup, select 'Backup' then reboot it with
sudo virsh reboot haosthen go HAOS settings -> Configure network interfaces -> IPv4 -> Static and set the IP address that was previously set . - Back on your terminal, activate the autostart:
sudo virsh autostart haos - Check autostart in:
sudo virsh dominfo haos
You need to differentiate between:
- HOST IP (the IP of the laptop):
ip addrexample:192.168.1.86 - VM-IP or HAOS-IP (the IP of the HAOS VM) example:
192.168.1.20orhomeassistant.local - Both have IPs assigned from the router. The HAOS VM uses a bridge, so it gets an IP from the router just like a regular device.
- These IPs can change if we restart the router, we will assign static ip to the VM.
If you want to resize the VM disk:
inside HA in your browser or mobile app¶
- In your browser, go to
http://<HOST-IP>:8123, for examplehttp://homeassistant.local:8123orhttp://192.168.1.86:8123. - Create an account.
- User settings: Profile -> General-> language, date format, number format, etc.
- Check Advanced mode in General settings.
- in User settings, go to Security -> Enable MFA
- In Settings, check for an update for HAOS. After the update it will restart the server.
- In the addon store, click on the three dots on the top right and add this repository: https://github.com/homeassistant-apps/repository
- Set up a static IP for the VM: in HAOS website go to Settings -> System -> Network -> and change IPv4 from auto to static and IPv6 too. You can keep the default ip that is assigned to the VM.
2. Accessing HAOS from outside your local network — Cloudflare Tunnel Setup¶
- Create a Domain or your domain is already managed by Cloudflare
- In HAOS, add the addon
file editor. - Follow this video: https://youtu.be/JGAKzzOmvxg
- Don't forget to update and save configuration.yaml
- Restart HAOS
- On you phone, when you add home assistant, add both local and external URL, so you can connect when you are outside
- Adding both local and external URL is important because the external URL is the one that will be used when you are outside your local network.
Add Cloudflare Access (Zero Trust) ⭐⭐⭐¶
This is the single biggest security upgrade. Before showing your Home Assistant app in your browser or mobile, it will force the user to first login via an email or something. If the User can't log in, he won't be able to see the app.
- You are protecting for example
ha.yourdomain.comGo to Cloudflare Zero Trust -> Access Controls -> Applications -> Self-hosted:
1️⃣ Basic Information (Top Section)¶
Application name
Use something clear:
- Home Assistant
- (or Home Assistant – Production if you like)
Session Duration
Recommended:
- 24 hours or You can put more
2️⃣ Add Public Hostname (IMPORTANT)¶
Click Add public hostname.
Fill in:
- Hostname:
ha.yourdomain.com - Path:
/* - Type: HTTP
👉 This is what actually puts the Access login page in front of Home Assistant.
Do NOT add private IP unless you know you need it.
3️⃣ Browser Rendering Settings¶
Leave this OFF / untouched.
(Home Assistant does NOT need SSH/RDP/VNC rendering)
4️⃣ Policies (MOST IMPORTANT PART)¶
Click Add a policy. It will open a new page, and need to go back to this page and refresh it to show the policies.
Policy 1 — Allow yourself (and family)
- Policy name: Allow Home Assistant Users
- Action: Allow
Rules (choose ONE of these methods):
✅ Best option: Email-based
- Include -> Emails ->
[email protected] - You can add multiple emails (family members).
✅ Alternative: Identity provider
If using Google / Microsoft / GitHub:
- You can for example add GitHub following this.
- Adding the integration automatically adds it as a login method to all the applications. Don't add the provider as a login method to the policy, otherwise, it will accept all the users using that provider.
- Keep the emails in the policy. https://developers.cloudflare.com/cloudflare-one/access-controls/policies/#require
- If you want to disable the login, create a policy with action allow and an include rule 'everyone'
❌ Do NOT use:
- “Allow everyone”
- IP-only rules (unless combined with email)
- Country-only rules alone
(Optional but recommended)
- Add Require MFA if available in your plan.
5️⃣ (Optional) Add a Second Policy – Emergency Lockdown¶
Advanced but useful:
- Policy name: Block High Risk Countries
- Action: Block
- Rule:
- Include -> Countries -> (countries you NEVER travel to)
Cloudflare evaluates Block after Allow, so this is safe.
6️⃣ Login Methods¶
You’ll see:
- Accept all available identity providers -> ON
Recommended setting:
✅ Leave this ON if:
- You trust future IdPs
- You are the only admin
OR
🔒 Turn OFF and explicitly select:
- Email OTP
This is slightly more secure, less future surprise.
Go back to the previous page to see the policies¶
Where you put your domain, choose the policies you added.
7️⃣ Save the Application ✅¶
Once saved:
- Visiting
ha.yourdomain.comshould show Cloudflare Access login. If it doesn't force refresh the page or clear cookies. - Only after login -> Home Assistant loads
If you do a restoration, or change something in cloudflare, and you see that you can't view home assistant, delete the cookies and try again.
Activate MFA¶
- In profile -> security, activate MFA
You should now have:
- ✅ Cloudflare Tunnel (no ports open)
- ✅ Cloudflare Access login in front of HA
- ✅ Email (or Google login or GitHub)
- ✅ MFA
- ✅ 1 month session
Home Assistant devices, add-ons, and integrations¶
- Integrations: built into Home Assistant (Settings -> Devices & services).
- Add-ons (Apps): services that run alongside Home Assistant OS (AdguardHome,Cloudflared...etc).
- Repositories: additional catalogs for community add-ons and integrations (example: HACS).
Notes:
- Always search for an open-source integration before using a proprietary app. For example: TP-link, Dreame, LG ThinQ, Bosch Home Connect have open-source integrations and are far better than the proprietary apps.
- Some devices still need their vendor app for initial setup or extra features (example: LG, Bosch). It’s fine to keep the app installed even if you control the device later in Home Assistant.
Matter over Wi-Fi¶
- TBD
Matter over Thread and Zigbee devices¶
- If the device does Matter over Thread, you will need a Thread Border Router (TBR) on your network. Personally, I recommend Sonoff MG24 . It even supports both Thread and Zigbee and the same time.
- Do not use Aqara M100 because it is proprietary and not well-supported in Home Assistant.
How to setup Sonoff MG24:
- Attach to usb port, run
usb-deviceson ubuntu and get Vendor and ProdID. - Run:
sh
sudo virsh attach-device haos /dev/stdin --persistent <<EOF
<hostdev mode='subsystem' type='usb' managed='yes'>
<source>
<vendor id='0x10c4'/>
<product id='0xea60'/>
</source>
</hostdev>
EOF
Or using the bus and device:
```sh
sudo virsh attach-device haos /dev/stdin --persistent <<EOF
EOF ```
- Run
sudo virsh reboot haos. - Go to Home Assistant -> Settings -> System -> Hardware -> All Hardware -> search for
Sonoff. - Flash it using this tutorial.
If you want only thread: flash Sonoff for thread only.
If you want both Thread controller and Zigbee, use Multipan with the MG24. Tutorials:
- French: https://sonoff.tech/fr-fr/blogs/news/how-to-use-multipan-in-home-assistant-with-sonoff-dongle
- English: https://www.sonoff.in/blog/product-guides-3/how-to-use-multipan-in-home-assistant-with-sonoff-zbdongle-e-36
- Deactivate ZHA, and add devices: https://youtu.be/KA4XvVhyCtE?t=112
- If you are facing an issue with onboarding not loading after submitting, check: https://community.home-assistant.io/t/smlight-slzb-06-new-installation-can-not-pass-the-zigbee2mqtt-onboarding-page/908462/2
Apps and Add-ons¶
Exposure model — what is reachable from outside the LAN¶
Only the Cloudflared tunnel publishes services, and nothing is port-forwarded. Every
other add-on port is LAN-only — note that the LAN is not a trust boundary (a compromised
add-on can reach every other add-on and Home Assistant itself on 8123), so "LAN-only" means
"not on the internet", not "safe".
| Public? | Service | Front door | Blocked at the edge (Cloudflare WAF rule) |
|---|---|---|---|
| yes — Access | Home Assistant | Cloudflare Access (email OTP) | — |
| yes — Access | Hermes WebUI | Cloudflare Access + add-on password |
— (whole hostname is credential-grade) |
| yes — Access | Navidrome | Cloudflare Access service token — the client sends CF-Access-Client-Id / CF-Access-Client-Secret (one token per user: the only per-person revoke path). No email policy, so a plain browser gets 403 |
nothing path-level — the token is the gate, and clients still need /rest/* |
| not installed | Vaultwarden | deleted for now — on reinstall it gets no Access (the Bitwarden apps cannot answer a challenge) | /admin*, POST /identity/accounts/register*, and disable signups in /admin (verified open: /api/config reported disableUserRegistration: false) |
| yes — app login | AIOStreams | none — app login (auth_required) |
/api/v1/status, /builtins/* |
The actual hostnames are whatever is configured in the Cloudflared add-on (external_hostname
additional_hosts) and are deliberately not repeated here — this page should not be a map of the tunnel.
AIOStreams is the only hostname with no Access layer at all — Stremio/Nuvio clients are header-less and cannot answer an Access challenge, so its own login is the gate. Where the client can send headers (Navidrome), use a service token instead: it is per-person revocable and keeps anonymous traffic out entirely.
Free-plan budget: 5 WAF custom rules + 1 rate-limit rule. Spend the rate limit on
Vaultwarden's /identity/* if it comes back — never on the streaming hostnames, where a
10-second counting window would hit legitimate playback. Navidrome's Access app carries no
email policy, so the web player returns 403; add an email-allow policy to that app if the
browser UI is ever needed.
What each add-on actually serves, and the full must-never-be-public path list, lives in the add-on READMEs: AIOStreams, Beets, Hermes WebUI, MediaFlow Proxy Light, Octo-Fiesta, Personal App.
-
AdGuard Home
- DNS and DHCP (do not forget to add IPv4 and IPv6 DNS servers to your router). Disable safe search.
- For IP address name resolving, check this section.
- Docs/repo: https://github.com/hassio-addons/addon-adguard-home
- Add Google and Cloudflare DNS servers (both IPV4 and IPV6) as 2nd and 3rd options in case the self-hosted AdGuard Home breaks.
- Make heavy tests (Restart the server, restart HA, restart the router, cut the power off...) after setting AdGuard Home because if something breaks, you will not be able to access your home assistant.
-
Cloudflared (Cloudflare Tunnel)
- Create tunnels for the addons that you want. For example an app running on http://homeassistant.local:8081
- ⚠️ Every entry added here is public: the tunnel is the only door, but it is a door. Add an entry only after reading the add-on's Exposure section, and check the exposure model for which hostnames also need an Access policy.
-
File editor :
-
Home Assistant Google Drive Backup
- Backup everything except media.
- Test the backup at least once: https://youtu.be/xXXW7sQ9rqs?t=274
- Backup keeps everything, even addons configuration (delete cookies if the login page is not showing on your domain name).
- Samba backup add-on: https://github.com/thomasmauerer/hassio-addons/tree/master/samba-backup
- After a restoration, everything should work as expected:
- Clean cookies in your browser because of Cloudflare.
- The Sonoff MG24 can be unrecognized: wait a bit; if still not recognized, detach it and attach it again.
-
Music/Audio addons :
- Navidrome
- Octo-Fiesta
- Use it with Octo-Fiesta (and Tidal as a backend)
- Music Assistant Addon
- Clients : https://www.navidrome.org/apps/
- For example Narjo for iOS
- Auto tagger : Beets add-on — no HTTP surface, no published port
- You can check this music server setup
- ⚠️ Remote access — Navidrome is the one exposed service that runs privileged. The
community add-on gets
CAP_SYS_ADMIN+CAP_DAC_READ_SEARCHand the host's block devices, so code execution inside it is a host-level compromise, not a music leak. Lower-risk ways to keep remote access:- run Navidrome on the Ubuntu server and point the tunnel entry at its LAN IP
(
http://192.168.x.x:4533) — the Cloudflared add-on can reach LAN addresses; - or keep it LAN-only and reach it over Tailscale (
hassio-addons/app-tailscale), then drop the Navidrome entry from the tunnel. Cloudflare Access service tokens are only an option for clients that can send custom headers — Symfonium, Amperfy and Nautiline can; Narjo, Substreamer and Feishin cannot.
- run Navidrome on the Ubuntu server and point the tunnel entry at its LAN IP
(
-
AIOStreams — Stremio/Nuvio add-on aggregator (debrid/usenet)
- Public by design on its own tunnel hostname: Stremio/Nuvio clients cannot answer an Access OTP challenge. Keep
authset +auth_required: true, then block/api/v1/statusand/builtins/*at the edge.
- Public by design on its own tunnel hostname: Stremio/Nuvio clients cannot answer an Access OTP challenge. Keep
-
MediaFlow Proxy Light — streaming proxy used by AIOStreams
- LAN-only, never publish. The proxy password covers
/proxy/*but not the UI/builder paths —/playlist/builderis an unauthenticated fetch primitive (and a LAN port scanner).
- LAN-only, never publish. The proxy password covers
-
- The WebUI is exposed only behind Cloudflare Access (plus its own
passwordoption)./api/*runs the agent in-process — i.e. arbitrary tool execution — andall_addon_configs:rwlets it read every other add-on's configuration, including the tunnel config.
- The WebUI is exposed only behind Cloudflare Access (plus its own
-
- LAN-only, never publish — no authentication anywhere: UI on
8080, FastAPI/docs+/openapi.jsonon8000(not host-published, but reachable from any co-resident container), CORS*with credentials, defaultSESSION_SECRET.
- LAN-only, never publish — no authentication anywhere: UI on
-
Vaultwarden (self-hosted Bitwarden backend)
- Lightweight, open-source reimplementation of the Bitwarden server API. All official Bitwarden clients (iOS, Android, Windows, macOS, browser extensions) work with it.
- Addon repo: https://github.com/hassio-addons/app-vaultwarden
- After installing, point your Bitwarden clients to your self-hosted server URL instead of
bitwarden.com. - ⚠️ Exposure: exposed on its own hostname with no Access login (the Bitwarden apps cannot answer an OTP challenge), so the master password is the only door. Disable public signups:
/admin→ General → uncheck "Allow new signups" → Save — this is what/api/configreports asdisableUserRegistration, and while it isfalseanyone on the internet can create an account on your instance. Clear the "Signup Domains Whitelist" at the same time (a non-empty whitelist overrides the signups setting). Then block/admin*andPOST /identity/accounts/register*at the edge — the admin panel stays reachable on the LAN athttp://homeassistant.local:7277/admin.
-
n8n Update the config of the addon if you want to use the (forms and chat...etc) since they rely on webhooks and the api :
- In network enable both ports.
- Update the config of the addon. Since we changed the N8N_EDITOR_BASE_URL, you will not be able to access it from HAOS Ingress. You must use the new address.
timezone: Europe/Paris
env_vars_list:
- "WEBHOOK_URL: http://homeassistant.local:8081/"
- "N8N_PATH: /"
- "N8N_EDITOR_BASE_URL: http://homeassistant.local:5678/"
cmd_line_args: ""
It is better to use an external WEBHOOK_URL for the api so external applications can use it.
-
Samba Backup
-
Advanced SSH & Web Terminal (replaces the official Terminal & SSH add-on)
- Add-on repository to add in HA: https://github.com/hassio-addons/repository
- Docs: https://github.com/hassio-addons/addon-ssh/blob/main/ssh/DOCS.md
- Web terminal in the sidebar + SSH server, with Docker CLI and Docker socket access built in
- Note: disable Protection mode to use Docker; set a
passwordand/orauthorized_keysin the configuration to log in; for SFTP file access, set the username torootand enablesftp: true - Authentication: SSH always requires login, even on your own network. With a
password, you are prompted to type it on every connection (ssh hassio@<ha-ip>). With anauthorized_keysentry, no prompt appears — the key proves your identity automatically (your client may ask for the key's passphrase once, or ssh-agent remembers it). If both are set, the key is tried first and the password acts as a fallback. Note: on iOS, the Files app supports SMB only (no SFTP) — for SFTP/SSH use Termius (or another SSH/SFTP client) instead.
This next section is about controllers / routers add-ons :
-
Home Assistant Matter Hub
- Repo: https://github.com/RiDDiX/home-assistant-matter-hub
- Tutorial (old version but should work): https://www.youtube.com/watch?v=-TMzuHFo_-g
- Notes:
- Transform any device into a Matter device.
- Use it with Alexa and Google Home. If Alexa doesn't discover the hub, use Google Home app, then add it to Alexa from Google Home.
- You need to add labels to entities (not devices).
- Rooms are assigned by hand in the assistant's app (nothing reads the HA area) — re-assign the room after anything that makes Alexa re-import the device.
- ⚠️ Renaming an entity ID re-creates its endpoint: the device keeps its name but loses its room, so a generic phrase ("turn off the light in the kitchen") answers this action isn't supported while the exact device name still works. Fix = set the room again in the assistant's app.
- ⚠️ A room phrase turns on/off every light and switch in that room — including exposed scripts and remotes (the assistant sees them as switches). Keep scripts, remotes and media players out of the rooms you talk to; shutters and the vacuum are other categories and can't be reached that way.
- Scripts are momentary (on → off a second later) and some Echos choke on that: after one runs, Alexa can stop answering for the whole bridge until restart. Entity Mapping → "Do not report the on/off flip after a run" avoids it.
- Alexa only matches the exposed entity's name: a French phrase like "lance le nettoyage" does nothing on the bridge. Create an Alexa routine that maps several spoken sentences ("lance le nettoyage", "nettoie la maison"…) to the exposed script as its action.
- Expect this mapping step until Alexa handles intent natively: routines are the glue between how you speak and the script name.
-
Matter Server
-
Mosquitto broker
-
Silicon Labs Multiprotocol
-
Silicon Labs Flasher (useful for multiprotocol / firmware)
-
SONOFF Dongle Flasher
-
Zigbee2MQTT
Integrations¶
-
AdGuard Home
- Add-on repo (service): https://github.com/hassio-addons/addon-adguard-home
-
Alexa Media Player
- Repo: https://github.com/alandtse/alexa_media_player
- Video guide you referenced: https://www.youtube.com/watch?v=TDdREzkigIE&t
- Docs: https://www.home-assistant.io/integrations/alexa_media_player/
-
Android TV Remote : Control your TV.
- Docs: https://www.home-assistant.io/integrations/androidtv_remote/
- A
remoteentity (keys, app launch) and amedia_player(power, volume). The same box also shows up through Google Cast under the same name, and that second entity is where the playing content comes from — this media player isassumed_stateand never knows it. Merge the two into one TV entity with a Universal Media Player before using them in a dashboard. - Turning on and turning off are the same
POWERkey — a toggle — sent over the network; there is no wake-on-LAN packet. Turn-on therefore only works while the device keeps its network/remote service alive in standby. If turn-off works but turn-on doesn't, check in this order: - How the streaming device is powered: wall outlet, never the TV's USB port (that port dies with the panel, so nothing is left to wake).
- Streaming device → Settings → System → Power & Energy → Network standby ON (plus Screenless service where offered). Eco/deep standby powers the radio down and nothing can reach the device.
- TV side: HDMI-CEC / "One Touch Play" ON for that HDMI input, and quick-start standby rather than eco — waking the streaming device must also switch the panel on.
- Last, the integration's own Limitations (and clear Android TV Remote Service storage on the device if it keeps flapping unavailable).
- Test that separates a state bug from a real wake failure: power the TV on with its own remote → turn it off from Home Assistant → turn it on from Home Assistant.
- If that last step works, nothing is broken: the entity is
assumed_stateandturn_ononly sends the key when it believes the device is off, so a stale "on" turns the action into a silent no-op — and sincePOWERis a toggle, Home Assistant's belief has to match the panel before the key means "on". Driving the off yourself resynchronises both sides and proves the standby connection is alive. - If it still fails, the device is not reachable in standby → fix points 1–3.
- If that last step works, nothing is broken: the entity is
- Apps and keys:
media_player.play_media(media_content_type: app+ package id, e.g.com.netflix.ninja) andremote.send_commandfor D-pad/menu. Ready-made remote card: see Dashboard and Cards.
-
Backup :
-
Brother Printer
-
Dreame vacuum
- Community integration repo: https://github.com/Tasshack/dreame-vacuum
- Do not use the version 1. Use version > 2 (supports multiple accounts: dreamehome, xiaomi, etc.).
- Maps for the vacuum:
- Script for voice assistant cleaning per room : https://github.com/Magnum9O/HA_BluePrints
-
Free Mobile : https://www.home-assistant.io/integrations/freemobile/
-
Freebox : https://www.home-assistant.io/integrations/freebox/
-
Google Cast
- Docs: https://www.home-assistant.io/integrations/cast/
- One
media_playerper Cast device. It gives the full playback detail (title, art, play/pause, duration) but an unreliable power state — an Android TV sitting on its home screen still readsoff. - For an Android TV / Google TV device, take the power/volume commands from Android TV Remote and use this integration only as the playback/browse source, combined through a Universal Media Player (see the Android TV Remote entry).
-
HACS: The best is to use Home Assistant Community Store integrations when available; they often have more features.
-
Home Assistant Supervisor
-
Home Connect Alt : https://github.com/ekutner/home-connect-hass for Bosch devices
-
Matter
-
Mobile App
-
MotionEye
-
MQTT
- Volets Profalux Zigbee: https://perso.aquilenet.fr/~sven337/francais/2023/06/02/Appairage-de-volets-Profalux-Zigbee.html
- Workstation control — power off, restart, sleep, lock, plus host sensors. Pick the branch that matches the OS:
- Windows 11 — HASS.Agent : the Windows side of this, and .NET/Windows-only (there is no Linux build). It publishes its sensors and command buttons over MQTT discovery, so they simply appear in Home Assistant.
- Linux (CachyOS) — Go Hass Agent : the Linux equivalent, packaged for Arch (
go-hass-agentin the AUR). Power off / reboot / suspend go throughsystemd-logindand come with the host sensors (CPU, memory, disks, network, hardware sensors, power profiles). It talks to Home Assistant's native-app API with a long-lived token, so it is not an MQTT device.- Run it as a user service and enable lingering (
loginctl enable-linger) — otherwise the power controls only work while somebody is logged in. - Powering the machine on is Wake on LAN's job (see that entry below): no agent can do it from a powered-off OS.
- Run it as a user service and enable lingering (
- Not an option any more: HASS Workstation Service — repository archived (Mar 2025) and Windows-installer-only, despite advertising cross-platform support.
-
Open Thread Border Router
-
SmartThinQ LGE Sensors : https://github.com/ollo69/ha-smartthinq-sensors
-
SmartThings : https://www.home-assistant.io/integrations/smartthings/
- Cloud integration for Samsung devices (the local/open-source route is preferred when the device supports it).
-
Sun
-
System Monitor
- CPU/RAM/disk/network load of the Home Assistant host itself, handy to alert on before an add-on or the VM starves.
-
Tapo: Cameras Control
-
Thread
-
TP-Link Smart Home
-
Wake on LAN
- Docs: https://www.home-assistant.io/integrations/wake_on_lan/
- One
buttonper configured MAC address: press = power that machine on. This is the only part of "turn my PC on" that works from a powered-off machine — an agent inside the OS cannot do it (see the MQTT entry above). - Machine side: BIOS "Wake on LAN" / "Power on by PCIe" enabled and ErP/deep-sleep disabled, wired Ethernet (WoWLAN over Wi-Fi is a different, far less reliable feature), and on Linux the NIC must still be armed for WoL after a clean shutdown (
ethtool -s <nic> wol g, persisted by a systemd unit or a NetworkManager connection setting). - Note the Freebox device tracker for the machine (
device_tracker.<pc>) already tells you whether it is really off before you blame the button — anot_homestate withlast_time_reachableis the confirmation. - Turning a machine off, restarting it or locking it is not this integration's job (see the MQTT entry above).
-
HA Label State: https://github.com/andrew-codechimp/HA-Label-State
- Create two helpers for : entities & addons(apps) with 4 status: off,unavailable, unknown, not_home (away=not_home in raw format)
- You need to always look at the raw format of the state to see the status of the entity or addon. For example IKEA water-leak sensor's translated status is 'Dry' but the raw format is 'off'. But when the detector is really off, the state is 'unavailable' so our automation can still be triggered.
- Example addons_offline.yaml & low_battery.yaml
Dashboard and Cards¶
- Auto entities : https://github.com/thomasloven/lovelace-auto-entities
- Custom sidebar: https://github.com/elchininet/custom-sidebar
- Kiosk Mode: https://github.com/NemesisRE/kiosk-mode
- Mushroom cards : https://github.com/piitaya/lovelace-mushroom#installation
-
Universal Remote Card : https://github.com/Nerwyn/universal-remote-card
- The whole Android TV remote in a single card — touchpad, power, volume and media keys — built on the
remoteentity for the keys and themedia_playerentity for power/volume. This is the card used on the TV remote dashboard view:
type: custom:universal-remote-card platform: Android TV remote_id: remote.chromecast_amine media_player_id: media_player.chromecast_amine_2 rows: - [back, power, home, menu, keyboard] - [touchpad, [volume_buttons]] - [rewind, previous, play_pause, next, fast_forward]power, the D-pad names and the media keys are built-in key codes, so nocustom_actionsblock is needed for a standard Android TV remote; add one only for buttons the TV does not expose by default.
- The whole Android TV remote in a single card — touchpad, power, volume and media keys — built on the
Automations, Scenes, Script, Helpers and Entities¶
- 🤖 Automation = When something happens → do something
- Always use entities instead of devices. Entities have unique names.
- 🎬 Scene = Rarely used. Set things to a predefined state. A scene is just a snapshot of states.
- Script: is a sequence of actions that can be triggered by an automation or manually.
- 👉 Automations trigger scenes
- An example : I want to arm the alarm when I leave my home
- Automation 1 : if my phone is not available in home assistant (using freebox device tracker) -> Call a script to arm the alarm,
- Script 1: turn on the alarm, turn on the lock of doors, activate the camera's high motion detections ..etc
- Automation 2: if the alarm is triggered -> send notifications, run sound on the alarm ...
-
Scripts are usually created so we can activate them manually or with an automation. They don't have conditions.
-
Helpers
-
Entities
- When you add an entity, you can select a default behavior (toggle , show info, etc..) . For example a camera move down button, the default behavior is to
show more info, but you can change it totoggleso when you press that button, it will move the camera. - Show offline devices and addons : Add a filter card that shows 'not_home' and 'unavailable' entities and addons. You need to go to an addon in devices, and enable the 'running' entity to be able to use it in the card.
- When you rename a device, you can auto recreate the IDs. Changing the entity id doesn't propagate in scripts and automation so before changing the id of an entity, click on related, open in new tabs all the related stuff , change the id then change the id in the related stuff
- Use labels to group entities. For example, an 'entity status' label and 'addon status' label to detect offline devices and addons without the need to add each device in the condition. You need the
- After changing which entities carry a label, reload the Label State helper's entry (Settings -> Devices & services -> Label State -> ⋮ -> Reload) — removing a label is not picked up live, so the helper keeps its old value until then.
- Every so often, review the entities you disabled earlier (or that an integration ships disabled): Settings -> Devices & services -> Entities -> filter "Disabled". Some are genuinely useful (diagnostics, extra sensors, buttons) and would otherwise never appear.
- Devices with the Home Assistant companion app also get a
device_trackerentity, which can end up with the same name as the one from the Freebox integration. I disable the companion-app tracker for my phones and rely on the Freebox one, so each person/device has a single name.
- When you add an entity, you can select a default behavior (toggle , show info, etc..) . For example a camera move down button, the default behavior is to
AdGuard Home address naming resolution using the router (Freebox)¶
Who hands out the addresses, and who publishes the names:
- One DHCP server, one reservation table. AdGuard Home serves DHCPv4 here, so every fixed IPv4 address belongs in AdGuard Home → DHCP settings → Add static lease (IP + MAC + hostname). The one exception is the machine that runs DNS/DHCP itself: it keeps a fixed address configured on its own OS (that is why the Home Assistant VM's IPv4 is set in HAOS) so it can boot without a lease.
- Reserve the address in DHCP instead of setting it on the device. A device that assigns its own address gets no lease, and a DHCP server only publishes names for the leases it hands out — that device is exactly the one whose name stops resolving. If a device genuinely must self-assign, give it a DNS rewrite instead.
- The published name is the DHCP hostname, appended to the local domain:
<hostname>.<local_domain_name>(lanby default, configurable inAdGuardHome.yaml), answered only from inside the LAN. - ⚠️ AdGuard does not tell clients which suffix to use (it sends no DHCP option 15), so only the full
<name>.<local-domain>resolves — a bare<name>works only where a search domain is set on the client. - IPv6 has no reservations yet: the Freebox still assigns IPv6 addresses (RA / SLAAC), so there is nothing to freeze in the DHCP table. Keep the DNS server the router advertises over IPv6 pointed at AdGuard, and let the sync script keep adding each MAC's IPv6 addresses to its AdGuard client — identifying a client by MAC only works while AdGuard is the DHCP server.
- A rename reaches DNS only. Renaming a device in the router or in AdGuard does not rename it in Home Assistant (device and entity names come from the integration — rename in Settings → Devices) nor in the Matter/Alexa exposure (re-sync the controller afterwards).
-
While AdGuard owns DHCP + DNS, the Freebox's own local-domain setting stays unused; it counts again only if the Freebox takes that role back, in which case the reservations move to Paramètres (mode avancé) → DHCP → baux permanents.
-
You must have ONLY ONE DHCP server on the network: AdGuard Home is the one (DHCPv4 + DNS), the Freebox DHCP is off.
- Order matters: in Freebox Settings -> DHCP, put the AdGuard DNS server first and save, then deactivate the DHCP server and save again.
- Check the address here
- Don't touch the IPV6 settings.
- ⚠️ A bridge reset changes the MAC addresses: re-check them in the AdGuard leases, recreate the affected static leases, then restart the network / devices.
- Forget stale LAN devices in the Free mobile app (Freebox OS -> devices -> forget), e.g. after a bridge/MAC change, a new VM or a replaced phone, so the router re-detects them with their current MAC/IP instead of keeping the old entry.
- Restart the router. (Make sure the router DHCP is off and the DNS is pointing to the AdGuard Home)
Sync the names from the Freebox (script + cron)¶
- The following will show you how to get the names of the devices in AdGuard Home using your router (this example uses the Freebox API).
- You will notice that your AdGuard Home (AGH) logs contains anonymous entries like "FREE SAS" or raw IPv6 addresses (e.g., fe80::...).
- We create a Python script on Ubuntu that will get all the information from the Freebox API and send it to the AdGuard Home API.
- Steps:
- In the addon configuration in HA: open the port 8082, you should access the dashboard without authentication on
HA-IP:8082. the username and password are your Home assistant username and password (you can create a user named 'adguard' for it). - Put the following scripts in the server:
- I am using Freebox pop as a router, so I use the script freebox_auth.py to get the token
uv run freebox_auth.py - then I use this script in the server to automatically update Adguard home: sync_adguard_home_ip_mac.py to resolve the IPv6 addresses of each device and to name the devices AdGuard does not know yet.
uv run sync_adguard_home_ip_mac.py - Make a cronjob for it by adding this line at the end of
crontab -e(change the path of uv and the script)
- In the addon configuration in HA: open the port 8082, you should access the dashboard without authentication on
- Check Settings -> Client settings -> Persistent clients settings .
- This script will run every minute. If it discovers a new device, it will update the log file
sync_agh.logwith the latest sync information. - ⚠️ The name it takes from the Freebox (
primary_name, i.e. the name you set on the device's page) is used only when it creates a client. For a device it already knows, the script matches the MAC address and refreshes the addresses only, so the AdGuard client keeps its old name: renaming a device in the Freebox does not rename it in AdGuard. To catch up, rename the client in AdGuard Home, or delete it and let the next run recreate it (deleting also drops the per-client settings you had on it).
Network topology and DNS flows¶
graph TB
subgraph FreeboxLAN["🌐 Freebox LAN — 192.168.1.0/24 (IPv4 leases by AdGuard Home · Freebox = gateway + IPv6)"]
Freebox["Freebox router<br/>192.168.1.254<br/>gateway (DHCPv4 off)"]
LANDevice["LAN device (phone, laptop...)<br/>192.168.1.x"]
end
subgraph KVMServer["🖥️ KVM Server (Ubuntu host) — 192.168.1.123"]
br0["br0 (Linux bridge)<br/>192.168.1.123/24<br/>carries LAN + HA VM (vnet2)"]
virbr0["virbr0 (libvirt NAT gw)<br/>192.168.122.1/24<br/>DOWN when no VM attached"]
docker0["docker0 (default bridge network)<br/>172.17.0.1/16<br/>unused, DOWN — no containers running"]
end
subgraph NATVMs["libvirt NAT — 192.168.122.0/24"]
natVM["KVM VM in NAT mode<br/>192.168.122.x"]
end
subgraph HAVM["🏠 Home Assistant OS VM — 192.168.1.119 (bridged; IPv4 fixed in HAOS)"]
AdGuard["AdGuard Home (addon, host network)<br/>192.168.1.119:53 DNS · :8082 web UI"]
subgraph Hassio1["hassio network — 172.30.32.0/23"]
hassioGw["gateway<br/>172.30.32.1"]
hassioDns["hassio_dns<br/>172.30.32.3<br/>HAOS internal DNS proxy"]
haAddons["other addons<br/>172.30.32.4+"]
end
subgraph Hassio2["2nd HAOS network — 172.30.33.0/23"]
hermes["Hermes Agent addon<br/>172.30.33.10"]
end
end
%% DNS flows
LANDevice -->|"direct DNS query"| AdGuard
haAddons -->|"internal query"| hassioDns
hermes -->|"internal query"| hassioDns
hassioDns -->|"forwarded DNS query"| AdGuard
natVM -->|"query"| virbr0
virbr0 -->|"forwarded DNS query"| AdGuard
%% topology / carrier links (not DNS)
Freebox -.->|LAN| br0
br0 -.->|bridged NIC vnet2| AdGuard
classDef visible stroke:#2e7d32,stroke-width:2px;
classDef invisible stroke:#c62828,stroke-width:2px,stroke-dasharray: 5 5;
class Freebox,LANDevice,br0,AdGuard visible;
class virbr0,docker0,natVM,hassioGw,hassioDns,haAddons,hermes invisible;
Legend:
- Solid border = IP visible to the Freebox API (DHCP leases / ARP table), i.e. any
192.168.1.xaddress on the physical LAN. - Dashed border = IP invisible to the Freebox API — it never appears in Freebox DHCP/ARP, only inside the server (
172.17.0.0/16docker0,192.168.122.0/24libvirt NAT) or inside the HA VM (172.30.32.0/23,172.30.33.0/23hassio networks). This is why containers/VMs on these ranges show up as "unknown"/"FREE SAS" in AdGuard until resolved another way. br0— Linux bridge on the KVM host; makes the host and the bridged HA VM appear as regular devices on the Freebox LAN (192.168.1.0/24).virbr0— libvirt's default NAT network (192.168.122.0/24); runs adnsmasqon.1that forwards DNS upstream to AdGuard for any VM placed in NAT mode instead of bridged mode.docker0— Docker's default bridge network (172.17.0.0/16) on the server. Confirmed viadocker network ls/docker ps -a: only the defaultbridge/host/nonenetworks exist and no containers are running, so this interface is currently unused (DOWN).- Direct vs forwarded — "direct" means the querying host talks straight to AdGuard (
192.168.1.119:53); "forwarded" means an intermediate resolver (hassio_dnsor libvirt'sdnsmasq) receives the query first and relays it to AdGuard.
todo¶
- backup vm
-
Lan architecture : Router Freebox = Honor Router 3 (bridge) - Ubuntu server (HAOS ( Adguard DNS + DHCP + Script for IPV6 update & naming resolution))
-
add cards : https://github.com/PiotrMachowski/lovelace-xiaomi-vacuum-map-card mushroom card auto entities kioske mode custom sidebar room card