fix(install): copy app subdirectories so dependency hooks ship
install_from() only copied top-level files, so an app's scripts/ folder — where provider on_install/on_start dependency hooks live — never reached /var/lib/furtka/apps/<app>/. Every hooked dependency therefore failed at reconcile with "hook ... missing in provider". Copy subdirectories too (rmtree+copytree so a reinstall drops files removed upstream). Found during the first real end-to-end run of the mosquitto+zigbee2mqtt dependency pair on a test VM. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1155f1d4ba
commit
0615d92c8d
2 changed files with 35 additions and 1 deletions
|
|
@ -171,13 +171,22 @@ def install_from(src: Path, settings: dict[str, str] | None = None) -> Path:
|
||||||
target.mkdir(parents=True, exist_ok=True)
|
target.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
for item in src.iterdir():
|
for item in src.iterdir():
|
||||||
|
dest = target / item.name
|
||||||
|
# Subdirectories (e.g. scripts/ holding a provider's on_install/on_start
|
||||||
|
# dependency hooks) must come along too — copy the whole tree, replacing
|
||||||
|
# any stale copy from a previous install so removed files don't linger.
|
||||||
|
if item.is_dir():
|
||||||
|
if dest.exists():
|
||||||
|
shutil.rmtree(dest)
|
||||||
|
shutil.copytree(item, dest)
|
||||||
|
continue
|
||||||
if not item.is_file():
|
if not item.is_file():
|
||||||
continue
|
continue
|
||||||
# Never overwrite an existing user .env — either settings-driven write
|
# Never overwrite an existing user .env — either settings-driven write
|
||||||
# or previous manual edit has authority.
|
# or previous manual edit has authority.
|
||||||
if item.name == ".env" and (target / ".env").exists():
|
if item.name == ".env" and (target / ".env").exists():
|
||||||
continue
|
continue
|
||||||
shutil.copy2(item, target / item.name)
|
shutil.copy2(item, dest)
|
||||||
|
|
||||||
env = target / ".env"
|
env = target / ".env"
|
||||||
env_example = target / ".env.example"
|
env_example = target / ".env.example"
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,31 @@ def test_install_from_copies_files(tmp_path, fake_dirs):
|
||||||
assert (target / ".env").read_text() == "A=1"
|
assert (target / ".env").read_text() == "A=1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_install_from_copies_subdirectories(tmp_path, fake_dirs):
|
||||||
|
# Dependency hooks live under scripts/ — the whole subtree must be copied,
|
||||||
|
# not just top-level files, or providers ship without their hooks.
|
||||||
|
src = _write_app_source(tmp_path, "mosquitto", VALID_MANIFEST, env_example="A=1")
|
||||||
|
scripts = src / "scripts"
|
||||||
|
scripts.mkdir()
|
||||||
|
(scripts / "provision-client.sh").write_text("#!/bin/sh\necho hi\n")
|
||||||
|
target = installer.install_from(src)
|
||||||
|
assert (target / "scripts" / "provision-client.sh").read_text() == "#!/bin/sh\necho hi\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_install_from_reinstall_drops_stale_subdir_files(tmp_path, fake_dirs):
|
||||||
|
# A reinstall whose source renamed/removed a hook must not leave the old one.
|
||||||
|
src = _write_app_source(tmp_path, "mosquitto", VALID_MANIFEST, env_example="A=1")
|
||||||
|
scripts = src / "scripts"
|
||||||
|
scripts.mkdir()
|
||||||
|
(scripts / "old-hook.sh").write_text("old\n")
|
||||||
|
installer.install_from(src)
|
||||||
|
(scripts / "old-hook.sh").unlink()
|
||||||
|
(scripts / "new-hook.sh").write_text("new\n")
|
||||||
|
target = installer.install_from(src)
|
||||||
|
assert not (target / "scripts" / "old-hook.sh").exists()
|
||||||
|
assert (target / "scripts" / "new-hook.sh").read_text() == "new\n"
|
||||||
|
|
||||||
|
|
||||||
def test_install_from_preserves_existing_env(tmp_path, fake_dirs):
|
def test_install_from_preserves_existing_env(tmp_path, fake_dirs):
|
||||||
src = _write_app_source(tmp_path, "fileshare", VALID_MANIFEST, env_example="A=new")
|
src = _write_app_source(tmp_path, "fileshare", VALID_MANIFEST, env_example="A=new")
|
||||||
target = apps_dir() / "fileshare"
|
target = apps_dir() / "fileshare"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue