diff --git a/flake.lock b/flake.lock
index f1194f2..8235dfe 100644
--- a/flake.lock
+++ b/flake.lock
@@ -35,11 +35,11 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1714076141,
-        "narHash": "sha256-Drmja/f5MRHZCskS6mvzFqxEaZMeciScCTFxWVLqWEY=",
+        "lastModified": 1714253743,
+        "narHash": "sha256-mdTQw2XlariysyScCv2tTE45QSU9v/ezLcHJ22f0Nxc=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "7bb2ccd8cdc44c91edba16c48d2c8f331fb3d856",
+        "rev": "58a1abdbae3217ca6b702f03d3b35125d88a2994",
         "type": "github"
       },
       "original": {
diff --git a/flake.nix b/flake.nix
index cdb503b..3f88bd3 100644
--- a/flake.nix
+++ b/flake.nix
@@ -111,6 +111,7 @@
           // (vm_test "audiobookshelf" ./test/vm/audiobookshelf.nix)
           // (vm_test "authelia" ./test/vm/authelia.nix)
           // (vm_test "grocy" ./test/vm/grocy.nix)
+          // (vm_test "home-assistant" ./test/vm/home-assistant.nix)
           // (vm_test "jellyfin" ./test/vm/jellyfin.nix)
           // (vm_test "ldap" ./test/vm/ldap.nix)
           // (vm_test "lib" ./test/vm/lib.nix)
diff --git a/lib/default.nix b/lib/default.nix
index 0d26ebf..8ff3515 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -29,12 +29,11 @@ rec {
       mkdir -p $(dirname ${templatePath})
       ln -fs ${file} ${templatePath}
       rm -f ${resultPath}
-      if [ -z "${sedPatterns}" ]; then
+      '' + (if sedPatterns == "" then ''
         cat ${templatePath} > ${resultPath}
-      else
+      '' else ''
         ${pkgs.gnused}/bin/sed ${sedPatterns} ${templatePath} > ${resultPath}
-      fi
-      '';
+      '');
 
   secretFileType = lib.types.submodule {
     options = {
diff --git a/modules/services/home-assistant.nix b/modules/services/home-assistant.nix
index 4593469..c708ed9 100644
--- a/modules/services/home-assistant.nix
+++ b/modules/services/home-assistant.nix
@@ -136,6 +136,42 @@ in
       };
     };
 
+    voice = lib.mkOption {
+      description = "Options related to voice service.";
+      default = {};
+      type = lib.types.submodule {
+        options = {
+          speech-to-text = lib.mkOption {
+            description = ''
+              Wyoming piper servers.
+
+              https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.piper.servers
+            '';
+            type = lib.types.attrsOf lib.types.anything;
+            default = {};
+          };
+          text-to-speech = lib.mkOption {
+            description = ''
+              Wyoming faster-whisper servers.
+
+              https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.faster-whisper.servers
+            '';
+            type = lib.types.attrsOf lib.types.anything;
+            default = {};
+          };
+          wakeword = lib.mkOption {
+            description = ''
+              Wyoming open wakework servers.
+
+              https://search.nixos.org/options?channel=23.11&from=0&size=50&sort=relevance&type=packages&query=services.wyoming.openwakeword
+            '';
+            type = lib.types.anything;
+            default = { enable = false; };
+          };
+        };
+      };
+    };
+
     backupCfg = lib.mkOption {
       type = lib.types.anything;
       description = "Backup configuration for home-assistant";
@@ -258,6 +294,10 @@ in
       };
     };
 
+    services.wyoming.piper.servers = cfg.voice.text-to-speech;
+    services.wyoming.faster-whisper.servers = cfg.voice.speech-to-text;
+    services.wyoming.openwakeword = cfg.voice.wakeword;
+
     services.nginx.virtualHosts."${fqdn}" = {
       http2 = true;
 
@@ -274,7 +314,7 @@ in
       };
     };
 
-    systemd.services.home-assistant.preStart = lib.mkIf cfg.ldap.enable (
+    systemd.services.home-assistant.preStart = lib.mkIf cfg.enable (
       let
         onboarding = pkgs.writeText "onboarding" ''
           {
@@ -289,16 +329,16 @@ in
             }
           }
         '';
-        storage = "${config.services.home-assistant.configDir}";
-        file = "${storage}/.storage/onboarding";
+        configDir = "${config.services.home-assistant.configDir}";
+        file = "${configDir}/.storage/onboarding";
       in
         ''
-          if ! -f ${file}; then
-            mkdir -p ${storage} && cp ${onboarding} ${file}
+          if ! [ -f ${file} ]; then
+            mkdir -p ${configDir}/.storage && cp ${onboarding} ${file}
           fi
         '' + shblib.replaceSecrets {
           userConfig = cfg.config;
-          resultPath = "${config.services.home-assistant.configDir}/secrets.yaml";
+          resultPath = "${configDir}/secrets.yaml";
           generator = name: value: lib.generators.toYAML {} value;
         });
 
diff --git a/test/vm/home-assistant.nix b/test/vm/home-assistant.nix
new file mode 100644
index 0000000..ff343c0
--- /dev/null
+++ b/test/vm/home-assistant.nix
@@ -0,0 +1,219 @@
+{ pkgs, lib, ... }:
+let
+  pkgs' = pkgs;
+
+  commonTestScript = { nodes, extraPorts ? [], ... }:
+    let
+      hasSSL = !(isNull nodes.server.shb.home-assistant.ssl);
+      fqdn = if hasSSL then "https://ha.example.com" else "http://ha.example.com";
+    in
+    ''
+    import json
+    import os
+    import pathlib
+
+    start_all()
+    server.wait_for_unit("home-assistant.service")
+    server.wait_for_open_port(${toString nodes.server.services.home-assistant.config.http.server_port})
+    ''
+    + lib.concatMapStringsSep "\n" (port: ''
+    server.wait_for_open_port(${toString port})
+    '') extraPorts
+    + ''
+
+    if ${if hasSSL then "True" else "False"}:
+        server.copy_from_vm("/etc/ssl/certs/ca-certificates.crt")
+        client.succeed("rm -r /etc/ssl/certs")
+        client.copy_from_host(str(pathlib.Path(os.environ.get("out", os.getcwd())) / "ca-certificates.crt"), "/etc/ssl/certs/ca-certificates.crt")
+
+    def curl(target, format, endpoint, succeed=True):
+        return json.loads(target.succeed(
+            "curl --fail-with-body --silent --show-error --output /dev/null --location"
+            + " --connect-to ha.example.com:443:server:443"
+            + " --connect-to ha.example.com:80:server:80"
+            + f" --write-out '{format}'"
+            + " " + endpoint
+        ))
+
+    print(server.succeed("cat /var/lib/hass/configuration.yaml"))
+    print(server.succeed("systemctl cat home-assistant"))
+    print(server.succeed("cat ''$(systemctl cat home-assistant | grep ExecStartPre | cut -d= -f2)"))
+    print(server.succeed("cat /var/lib/hass/secrets.yaml.template"))
+    print(server.succeed("cat /var/lib/hass/secrets.yaml"))
+
+    with subtest("access"):
+        response = curl(client, """{"code":%{response_code}}""", "${fqdn}")
+
+        if response['code'] != 200:
+            raise Exception(f"Code is {response['code']}")
+    '';
+
+  modules = {
+    base = {
+      imports = [
+        (pkgs'.path + "/nixos/modules/profiles/headless.nix")
+        (pkgs'.path + "/nixos/modules/profiles/qemu-guest.nix")
+        ../../modules/blocks/nginx.nix
+        ../../modules/services/home-assistant.nix
+      ];
+
+      # Nginx port.
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      # VM needs a bit more memory than default.
+      # virtualisation.memorySize = 4096;
+    };
+
+    basic = {
+      imports = [
+        {
+          options = {
+            shb.backup = lib.mkOption { type = lib.types.anything; };
+            shb.authelia = lib.mkOption { type = lib.types.anything; };
+          };
+        }
+      ];
+
+      shb.home-assistant = {
+        enable = true;
+        domain = "example.com";
+        subdomain = "ha";
+
+        config = {
+          name = "SHB Test";
+          country = "BE"; # https://en.wikipedia.org/wiki/ISO_3166-1
+          latitude = "0";
+          longitude = "0";
+          time_zone.source = pkgs.writeText "timeZoneSecret" "UTC"; # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+          unit_system = "metric";
+        };
+      };
+    };
+
+    ldap = { config, ... }: {
+      imports = [
+        ../../modules/blocks/ldap.nix
+      ];
+
+      shb.ldap = {
+        enable = true;
+        domain = "example.com";
+        subdomain = "ldap";
+        ldapPort = 3890;
+        webUIListenPort = 17170;
+        dcdomain = "dc=example,dc=com";
+        ldapUserPasswordFile = pkgs.writeText "ldapUserPassword" "ldapUserPassword";
+        jwtSecretFile = pkgs.writeText "jwtSecret" "jwtSecret";
+      };
+
+      shb.home-assistant = {
+        ldap = {
+          enable = true;
+          host = "127.0.0.1";
+          port = config.shb.ldap.ldapPort;
+          userGroup = "homeassistant_user";
+        };
+      };
+    };
+
+    voice = {
+      shb.home-assistant.voice.text-to-speech = {
+        "fr" = {
+          enable = true;
+          voice = "fr-siwis-medium";
+          uri = "tcp://0.0.0.0:10200";
+          speaker = 0;
+        };
+        "en" = {
+          enable = true;
+          voice = "en_GB-alba-medium";
+          uri = "tcp://0.0.0.0:10201";
+          speaker = 0;
+        };
+      };
+      shb.home-assistant.voice.speech-to-text = {
+        "tiny-fr" = {
+          enable = true;
+          model = "base-int8";
+          language = "fr";
+          uri = "tcp://0.0.0.0:10300";
+          device = "cpu";
+        };
+        "tiny-en" = {
+          enable = true;
+          model = "base-int8";
+          language = "en";
+          uri = "tcp://0.0.0.0:10301";
+          device = "cpu";
+        };
+      };
+      shb.home-assistant.voice.wakeword = {
+        enable = true;
+        uri = "tcp://127.0.0.1:10400";
+        preloadModels = [
+          "alexa"
+          "hey_jarvis"
+          "hey_mycroft"
+          "hey_rhasspy"
+          "ok_nabu"
+        ];
+      };
+    };
+  };
+in
+{
+  basic = pkgs.testers.runNixOSTest {
+    name = "home-assistant-basic";
+
+    nodes.server = { config, pkgs, ... }: {
+      imports = [
+        modules.base
+        modules.basic
+      ];
+    };
+
+    nodes.client = {};
+
+    testScript = commonTestScript;
+  };
+
+  ldap = pkgs.testers.runNixOSTest {
+    name = "home-assistant-ldap";
+
+    nodes.server = { config, pkgs, ... }: {
+      imports = [
+        modules.base
+        modules.basic
+        modules.ldap
+      ];
+    };
+
+    nodes.client = {};
+
+    testScript = commonTestScript;
+  };
+
+  voice = pkgs.testers.runNixOSTest {
+    name = "home-assistant-basic";
+
+    nodes.server = { config, pkgs, ... }: {
+      imports = [
+        modules.base
+        modules.basic
+        modules.voice
+      ];
+    };
+
+    nodes.client = {};
+
+    testScript = { nodes, ... }: commonTestScript {
+      inherit nodes;
+      extraPorts = [
+        10200
+        10201
+        # 10300
+        # 10301
+        10400
+      ];
+    };
+  };
+}