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.
This commit is contained in:
Martin Weinelt 2025-06-12 06:02:32 +02:00
parent 035a957c57
commit 19625ae802
No known key found for this signature in database
GPG Key ID: 87C1E9888F856759
3 changed files with 63 additions and 10 deletions

View File

@ -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'")

View File

@ -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}";

View File

@ -0,0 +1,35 @@
commit cca718816f66b0155602c974e6743e8ce49e367b
Author: Martin Weinelt <hexa@darmstadt.ccc.de>
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)