From 5eaed595845626f9a328ce3655dda26668948048 Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Tue, 20 Oct 2026 19:37:26 +0200 Subject: [PATCH] fix: show only the current FTW beta in Home Assistant --- .github/workflows/check.yml | 16 --------------- COMPATIBILITY.md | 2 ++ README.md | 5 +++-- RELEASING.md | 16 +++++++++++++-- ftw-0x-beta/CHANGELOG.md | 2 ++ ftw-0x-beta/README.md | 2 +- ftw-0x-beta/config.yaml | 1 - ftw-beta/README.md | 5 +++-- ftw-beta/{config.yaml => retired.yaml} | 0 ftw/README.md | 4 ++-- scripts/validate.py | 22 ++++++++++++++++++-- tests/test_upstream_sync.py | 2 +- tests/test_validate.py | 28 +++++++++++++++++++++++++- 13 files changed, 75 insertions(+), 30 deletions(-) rename ftw-beta/{config.yaml => retired.yaml} (100%) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ae0f224..533fae1 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -47,25 +47,9 @@ jobs: - uses: actions/checkout@v7.0.0 with: persist-credentials: false - - uses: frenck/action-addon-linter@v2.21 - with: - path: ./ftw-beta - uses: frenck/action-addon-linter@v2.21 with: path: ./ftw-0x-beta - - id: stable - name: Detect the stable app - run: | - if [ -f ftw/config.yaml ]; then - echo "exists=true" >>"${GITHUB_OUTPUT}" - else - echo "exists=false" >>"${GITHUB_OUTPUT}" - echo "::notice::ftw/config.yaml is absent until the first stable promotion" - fi - - uses: frenck/action-addon-linter@v2.21 - if: steps.stable.outputs.exists == 'true' - with: - path: ./ftw prepare-build: name: Prepare native build matrix diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index d35db23..176c858 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -23,6 +23,8 @@ qualification yet. Follow [the pilot checks](pilot/README.md). The records below describe the retired app, not current install recommendations. 2.x and 3.x receive no more updates. The app does not follow new 0.x; see [the install choices](README.md). +The store no longer lists these apps. The last beta manifest is kept in +`ftw-beta/retired.yaml`, which Supervisor does not load as an app. `compatibility.yaml` (schema 2) is the source of truth for what each app installs. The app version mirrors the FTW Core version it runs. diff --git a/README.md b/README.md index b8e04c2..544a4ac 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ Open **Settings → Apps → Install app → ⋮ → Repositories**, add `https://github.com/srcfl/home-assistant-addons`, install **FTW (0.x beta)**, start it and choose **Open web UI**. -The old **FTW (beta)** and **FTW** apps below stay on the retired 3.x line and -are unchanged. +The app store offers only **FTW (0.x beta)**. The retired 3.x apps are no +longer listed for new installs. Existing installations and their data stay +on the device; keep a backup before moving to the new app. ## Alternative: FTW on a separate Linux host diff --git a/RELEASING.md b/RELEASING.md index 607d8f1..f102e91 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -4,6 +4,12 @@ Sourceful maintains this repository. Fredrik owns FTW's product direction. The app slug is `ftw-0x-beta`; its name is **FTW (0.x beta)**. +Home Assistant has no `beta` stage. Keep the beta name and version, and omit +`stage` so the store does not add an Experimental flag. This does not promote +FTW to stable. +An already installed app keeps its old metadata until its next update or +rebuild. Reloading the store applies the new store listing; no reinstall is +needed. 1. Core publishes a native 0.x beta with both Linux packages, their checksums and `ftw-native-release.json`. @@ -43,6 +49,12 @@ recovery. Their presence does not authorize publication. The [previous release procedure](https://github.com/srcfl/home-assistant-addons/blob/82172cec84d7d756e35ed40da48e82c59ef5a6f1/RELEASING.md) describes that historical pipeline; it is not the current release policy. -Keep the old apps and their data available for recovery. Do not remove them -as part of the 0.x beta launch. New installs use +The old apps are no longer listed in the store. `ftw-beta/retired.yaml` keeps +the last manifest for recovery; do not rename it to `config.yaml`, `config.yml` +or `config.json`. Supervisor would list it again, even in a nested folder. +The old `ftw` stable app has no manifest and was never published. + +Removing a store entry does not uninstall an existing app or delete its data. +HA may flag the old app as removed from its repository. Keep its backup and +published images for recovery. New installs use [FTW (0.x beta)](ftw-0x-beta/DOCS.md), or the separate-host route in the README. diff --git a/ftw-0x-beta/CHANGELOG.md b/ftw-0x-beta/CHANGELOG.md index 5215e7c..abe4b83 100644 --- a/ftw-0x-beta/CHANGELOG.md +++ b/ftw-0x-beta/CHANGELOG.md @@ -3,3 +3,5 @@ ## 0.154.1-beta.1 - First FTW 0.x beta app, built from the v0.154.1-beta.1 release package. +- Show this beta in the app store without Home Assistant's Experimental flag. +- Remove the retired 3.x app from the store for new installs. diff --git a/ftw-0x-beta/README.md b/ftw-0x-beta/README.md index de70122..3d1388c 100644 --- a/ftw-0x-beta/README.md +++ b/ftw-0x-beta/README.md @@ -1,6 +1,6 @@ # FTW (0.x beta) New FTW 0.x for Home Assistant OS, built on the device from the checksummed -release package of a pinned FTW beta. Experimental: run it on one site at a +release package of a pinned FTW beta. Beta: run it on one site at a time, and stop any other FTW (old app or separate host) before this one controls equipment. See [DOCS.md](DOCS.md). diff --git a/ftw-0x-beta/config.yaml b/ftw-0x-beta/config.yaml index 5836b8b..5505799 100644 --- a/ftw-0x-beta/config.yaml +++ b/ftw-0x-beta/config.yaml @@ -6,7 +6,6 @@ url: https://github.com/srcfl/home-assistant-addons/tree/main/ftw-0x-beta arch: - amd64 - aarch64 -stage: experimental host_network: true ports: 8080/tcp: 8080 diff --git a/ftw-beta/README.md b/ftw-beta/README.md index 3fdfaa7..0fc8bb5 100644 --- a/ftw-beta/README.md +++ b/ftw-beta/README.md @@ -1,8 +1,9 @@ # FTW (beta) — old release line 2.x and 3.x receive no further updates. This app does not follow new 0.x. -Do not install the 3.x beta to get current FTW. No new-line app has shipped; -the stable app remains unpublished. +This app is no longer in the store. Its last manifest is kept in +`retired.yaml` for recovery. New installs use +[FTW (0.x beta)](../ftw-0x-beta/DOCS.md); the old stable app was never published. Use [Install and update FTW](https://github.com/srcfl/ftw/blob/master/docs/native-beta.md) to run current FTW on a separate Linux host. Keep a backup of this app and diff --git a/ftw-beta/config.yaml b/ftw-beta/retired.yaml similarity index 100% rename from ftw-beta/config.yaml rename to ftw-beta/retired.yaml diff --git a/ftw/README.md b/ftw/README.md index 95b6dd7..b8310d5 100644 --- a/ftw/README.md +++ b/ftw/README.md @@ -1,8 +1,8 @@ # FTW — old release line 2.x and 3.x receive no further updates. This app does not follow new 0.x. -Do not install the 3.x beta to get current FTW. No new-line app has shipped; -the stable app remains unpublished. +This stable app was never published. New installs use +[FTW (0.x beta)](../ftw-0x-beta/DOCS.md). Use [Install and update FTW](https://github.com/srcfl/ftw/blob/master/docs/native-beta.md) to run current FTW on a separate Linux host. Keep a backup of this app and diff --git a/scripts/validate.py b/scripts/validate.py index a88a4da..245f7d2 100755 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Validate the repository, both app manifests and the compatibility record.""" +"""Validate the app catalog and retired compatibility records.""" from __future__ import annotations @@ -83,6 +83,23 @@ def validate_repository() -> None: require(dockerfile.is_file(), "ftw/Dockerfile is the shared build context and must exist") validate_core_image_tag_contract(dockerfile.read_text(encoding="utf-8")) require(not (ROOT / "ftw-beta" / "Dockerfile").exists(), "ftw-beta must not carry its own Dockerfile") + validate_catalog() + + +def validate_catalog() -> None: + # Supervisor finds config files at any depth, skipping hidden and rootfs folders. + manifests = { + path.relative_to(ROOT).as_posix() + for path in ROOT.glob("**/config.*") + if path.suffix in (".yaml", ".yml", ".json") + and not any(part.startswith(".") or part == "rootfs" for part in path.relative_to(ROOT).parts) + } + require( + manifests == {"ftw-0x-beta/config.yaml"}, + f"only ftw-0x-beta may be listed in the app store, found {sorted(manifests)}", + ) + config = load_yaml(ROOT / "ftw-0x-beta/config.yaml") + require("stage" not in config, "native beta must omit stage; Home Assistant has no beta stage") def validate_core_pin(core: Any, channel: str) -> None: @@ -222,7 +239,8 @@ def validate_channel(channel: str, compat: dict[str, Any]) -> None: validate_beta(compat) else: validate_stable(compat, required=True) - config = load_yaml(add_on_directory(channel) / "config.yaml") + filename = "retired.yaml" if channel == "beta" else "config.yaml" + config = load_yaml(add_on_directory(channel) / filename) validate_add_on_config(config, compat, channel) diff --git a/tests/test_upstream_sync.py b/tests/test_upstream_sync.py index 25d90d4..7d0674f 100644 --- a/tests/test_upstream_sync.py +++ b/tests/test_upstream_sync.py @@ -87,7 +87,7 @@ def load_repository() -> tuple[dict, str, str]: root = upstream_sync.ROOT return ( upstream_sync.load_yaml(root / "compatibility.yaml"), - (root / "ftw-beta/config.yaml").read_text(encoding="utf-8"), + (root / "ftw-beta/retired.yaml").read_text(encoding="utf-8"), (root / "ftw-beta/CHANGELOG.md").read_text(encoding="utf-8"), ) diff --git a/tests/test_validate.py b/tests/test_validate.py index e1db814..ae34fa9 100644 --- a/tests/test_validate.py +++ b/tests/test_validate.py @@ -1,7 +1,10 @@ from __future__ import annotations import copy +import pathlib +import tempfile import unittest +from unittest import mock import yaml @@ -16,7 +19,7 @@ def load_repository() -> tuple[dict, dict, str]: compat = validate.load_yaml(validate.ROOT / "compatibility.yaml") - beta_text = (validate.ROOT / "ftw-beta/config.yaml").read_text(encoding="utf-8") + beta_text = (validate.ROOT / "ftw-beta/retired.yaml").read_text(encoding="utf-8") return compat, yaml.safe_load(beta_text), beta_text @@ -50,6 +53,29 @@ def setUp(self) -> None: def test_repository_layout_passes(self) -> None: validate.validate_repository() + def test_retired_app_cannot_return_to_the_store(self) -> None: + for filename in ("config.yaml", "config.yml", "config.json"): + with self.subTest(filename=filename), tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + (root / "ftw-0x-beta").mkdir() + (root / "ftw-0x-beta/config.yaml").write_text("name: FTW (0.x beta)\n") + old = root / "archive/ftw-beta" + old.mkdir(parents=True) + (old / filename).write_text("{}\n") + with mock.patch.object(validate, "ROOT", root): + with self.assertRaisesRegex(validate.ValidationError, "only ftw-0x-beta"): + validate.validate_catalog() + + def test_native_beta_does_not_use_a_stage_flag(self) -> None: + for stage in ("experimental", "beta"): + with self.subTest(stage=stage), tempfile.TemporaryDirectory() as directory: + root = pathlib.Path(directory) + (root / "ftw-0x-beta").mkdir() + (root / "ftw-0x-beta/config.yaml").write_text(f"stage: {stage}\n") + with mock.patch.object(validate, "ROOT", root): + with self.assertRaisesRegex(validate.ValidationError, "must omit stage"): + validate.validate_catalog() + def test_dockerfile_binds_the_image_tag_to_the_core_version(self) -> None: validate.validate_core_image_tag_contract(self.dockerfile) for version in ("v3.4.2-beta.4", "v3.4.2"):