From 19625ae802a90629f4f8422a4e10eea4a87fdbe1 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Thu, 12 Jun 2025 06:02:32 +0200 Subject: [PATCH] home-assistant: reset permissions when copying default blueprints This fixes the import of backups, that would break when they wanted to nuke the existing config, because they had to npermission to delete the default blueprints that were copied without write-permissions from the nix store. --- nixos/tests/home-assistant.nix | 35 +++++++++++++------ pkgs/servers/home-assistant/default.nix | 3 ++ .../default-blueprint-permissions.patch | 35 +++++++++++++++++++ 3 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 pkgs/servers/home-assistant/patches/default-blueprint-permissions.patch diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index ce2cf17fab56..55664bfa0d79 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -128,16 +128,22 @@ in ]; }; lovelaceConfigWritable = true; + }; - blueprints.automation = [ - (pkgs.fetchurl { - url = "https://github.com/home-assistant/core/raw/2025.1.4/homeassistant/components/automation/blueprints/motion_light.yaml"; - hash = "sha256-4HrDX65ycBMfEY2nZ7A25/d3ZnIHdpHZ+80Cblp+P5w="; - }) - ]; - blueprints.template = [ - "${pkgs.home-assistant.src}/homeassistant/components/template/blueprints/inverted_binary_sensor.yaml" - ]; + # Add blueprints next, because we want to test the installation of the default blueprints first + specialisation.addBlueprints = { + inheritParentConfig = true; + configuration.services.home-assistant = { + blueprints.automation = [ + (pkgs.fetchurl { + url = "https://github.com/home-assistant/core/raw/2025.1.4/homeassistant/components/automation/blueprints/motion_light.yaml"; + hash = "sha256-4HrDX65ycBMfEY2nZ7A25/d3ZnIHdpHZ+80Cblp+P5w="; + }) + ]; + blueprints.template = [ + "${pkgs.home-assistant.src}/homeassistant/components/template/blueprints/inverted_binary_sensor.yaml" + ]; + }; }; # Cause a configuration change inside `configuration.yml` and verify that the process is being reloaded. @@ -237,7 +243,16 @@ in with subtest("Check extra components are considered in systemd unit hardening"): hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB") - with subtest("Check that blueprints are installed"): + with subtest("Check that default blueprints are copied writable"): + hass.succeed("stat -c '%a' ${configDir}/blueprints/automation/homeassistant | grep 700") + hass.succeed("stat -c '%a' ${configDir}/blueprints/automation/homeassistant/motion_light.yaml | grep 600") + # Delete blueprints, so we can check the declarative setup next + hass.execute("rm -rf ${configDir}/blueprints") + + with subtest("Check that configured blueprints are installed"): + cursor = get_journal_cursor() + hass.succeed("${system}/specialisation/addBlueprints/bin/switch-to-configuration test") + wait_for_homeassistant(cursor) hass.succeed("test -L '${configDir}/blueprints/automation/motion_light.yaml'") hass.succeed("test -L '${configDir}/blueprints/template/inverted_binary_sensor.yaml'") diff --git a/pkgs/servers/home-assistant/default.nix b/pkgs/servers/home-assistant/default.nix index 8d286ae30426..b6f6b88a5517 100644 --- a/pkgs/servers/home-assistant/default.nix +++ b/pkgs/servers/home-assistant/default.nix @@ -432,6 +432,9 @@ python.pkgs.buildPythonApplication rec { # Follow symlinks in /var/lib/hass/www ./patches/static-follow-symlinks.patch + # Copy default blueprints without preserving permissions + ./patches/default-blueprint-permissions.patch + # Patch path to ffmpeg binary (replaceVars ./patches/ffmpeg-path.patch { ffmpeg = "${lib.getExe ffmpeg-headless}"; diff --git a/pkgs/servers/home-assistant/patches/default-blueprint-permissions.patch b/pkgs/servers/home-assistant/patches/default-blueprint-permissions.patch new file mode 100644 index 000000000000..df802b2828b9 --- /dev/null +++ b/pkgs/servers/home-assistant/patches/default-blueprint-permissions.patch @@ -0,0 +1,35 @@ +commit cca718816f66b0155602c974e6743e8ce49e367b +Author: Martin Weinelt +Date: Thu Jun 12 03:37:27 2025 +0200 + + blueprints: copy default blueprints w/o permissions + + There is no easy way to achieve this with shutil, because copytree() will + always call copystat() an ruin directory permissions. + +diff --git a/homeassistant/components/blueprint/models.py b/homeassistant/components/blueprint/models.py +index 88052100259..a0b29ed3d01 100644 +--- a/homeassistant/components/blueprint/models.py ++++ b/homeassistant/components/blueprint/models.py +@@ -378,9 +378,17 @@ class DomainBlueprints: + if self.blueprint_folder.exists(): + return + +- shutil.copytree( +- integration.file_path / BLUEPRINT_FOLDER, +- self.blueprint_folder / HOMEASSISTANT_DOMAIN, +- ) ++ import os ++ os.makedirs(self.blueprint_folder / HOMEASSISTANT_DOMAIN) ++ ++ import subprocess ++ subprocess.check_call([ ++ "cp", ++ "--no-preserve=mode", ++ "--no-target-directory", ++ "--recursive", ++ str(integration.file_path / BLUEPRINT_FOLDER), ++ str(self.blueprint_folder / HOMEASSISTANT_DOMAIN), ++ ]) + + await self.hass.async_add_executor_job(populate)