ci: migrate workflows and releases to Gitea
This commit is contained in:
@@ -5,7 +5,8 @@ on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
hassfest:
|
||||
@@ -15,4 +16,4 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Hassfest validation
|
||||
uses: home-assistant/actions/hassfest@master
|
||||
uses: https://github.com/home-assistant/actions/hassfest@master
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
releases: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create relaytv.zip
|
||||
run: |
|
||||
cd custom_components/relaytv
|
||||
zip -r ../../relaytv.zip . -x "__pycache__/*" "*/__pycache__/*" "*.pyc"
|
||||
|
||||
- name: Create Gitea release
|
||||
env:
|
||||
GITEA_REF_NAME: ${{ gitea.ref_name }}
|
||||
GITEA_REPOSITORY: ${{ gitea.repository }}
|
||||
GITEA_SERVER_URL: ${{ gitea.server_url }}
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
run: bash scripts/publish-gitea-release.sh relaytv.zip RELEASE_NOTES.md
|
||||
@@ -5,7 +5,8 @@ on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -0,0 +1,19 @@
|
||||
name: Validate
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate-repository:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate integration repository structure
|
||||
run: python3 scripts/validate-repository.py
|
||||
@@ -1,26 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Create relaytv.zip for HACS
|
||||
run: |
|
||||
cd custom_components/relaytv
|
||||
zip -r ../../relaytv.zip . -x "__pycache__/*" "*/__pycache__/*" "*.pyc"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body_path: RELEASE_NOTES.md
|
||||
files: relaytv.zip
|
||||
@@ -1,19 +0,0 @@
|
||||
name: Validate
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
validate-hacs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: HACS validation
|
||||
uses: hacs/action@main
|
||||
with:
|
||||
category: integration
|
||||
@@ -18,7 +18,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/mcgeezy/relaytv-ha/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/mcgeezy/relaytv-ha/tests.yml?branch=main&label=tests&logo=github" alt="Tests status"></a>
|
||||
<a href="https://git.relaytv.app/RelayTV/RelayTV-HA/actions/workflows/tests.yml"><img src="https://git.relaytv.app/RelayTV/RelayTV-HA/actions/workflows/tests.yml/badge.svg?branch=main" alt="Tests status"></a>
|
||||
<a href="https://github.com/hacs/integration"><img src="https://img.shields.io/badge/HACS-custom-41BDF5?logo=homeassistantcommunitystore&logoColor=white" alt="HACS custom repository"></a>
|
||||
<a href="#compatibility"><img src="https://img.shields.io/badge/Home%20Assistant-2026.7%2B-41BDF5?logo=homeassistant&logoColor=white" alt="Home Assistant 2026.7 or newer"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-GPL--3.0--only-blue" alt="GPL-3.0-only license"></a>
|
||||
@@ -79,14 +79,22 @@ poll as the reconnect fallback.
|
||||
|
||||
### HACS
|
||||
|
||||
> HACS currently supports only public GitHub repositories; it cannot install
|
||||
> directly from this Gitea repository. Keep the
|
||||
> [`mcgeezy/relaytv-ha`](https://github.com/mcgeezy/relaytv-ha) mirror and its
|
||||
> releases current if you want to continue distributing through HACS.
|
||||
|
||||
1. Open **HACS** in Home Assistant.
|
||||
2. Add this repository as a custom repository with category **Integration**.
|
||||
2. Add `https://github.com/mcgeezy/relaytv-ha` as a custom repository with
|
||||
category **Integration**.
|
||||
3. Install **RelayTV** and restart Home Assistant.
|
||||
4. Add the integration from **Settings → Devices & Services → Add Integration**.
|
||||
|
||||
### Manual
|
||||
|
||||
1. Copy `custom_components/relaytv` into your Home Assistant config:
|
||||
1. Download and extract `relaytv.zip` from the Gitea release, or copy
|
||||
`custom_components/relaytv` from the repository, into your Home Assistant
|
||||
config:
|
||||
|
||||
```text
|
||||
/config/custom_components/relaytv/
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
"dependencies": [
|
||||
"frontend"
|
||||
],
|
||||
"documentation": "https://github.com/mcgeezy/relaytv-ha",
|
||||
"documentation": "https://git.relaytv.app/RelayTV/RelayTV-HA",
|
||||
"iot_class": "local_polling",
|
||||
"issue_tracker": "https://github.com/mcgeezy/relaytv-ha/issues",
|
||||
"issue_tracker": "https://git.relaytv.app/RelayTV/RelayTV-HA/issues",
|
||||
"requirements": [],
|
||||
"version": "0.4.0"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
archive_path="${1:-relaytv.zip}"
|
||||
notes_path="${2:-RELEASE_NOTES.md}"
|
||||
|
||||
for variable_name in GITEA_SERVER_URL GITEA_REPOSITORY GITEA_REF_NAME GITEA_TOKEN; do
|
||||
if [[ -z "${!variable_name:-}" ]]; then
|
||||
echo "Required environment variable is not set: ${variable_name}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -f "$archive_path" ]]; then
|
||||
echo "Release archive does not exist: $archive_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$notes_path" ]]; then
|
||||
echo "Release notes do not exist: $notes_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
api_base="${GITEA_SERVER_URL%/}/api/v1/repos/${GITEA_REPOSITORY}/releases"
|
||||
asset_name="${archive_path##*/}"
|
||||
authorization_header="Authorization: token ${GITEA_TOKEN}"
|
||||
|
||||
release_payload="$({
|
||||
RELEASE_NOTES_PATH="$notes_path" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
tag = os.environ["GITEA_REF_NAME"]
|
||||
notes = Path(os.environ["RELEASE_NOTES_PATH"]).read_text(encoding="utf-8")
|
||||
print(json.dumps({
|
||||
"body": notes,
|
||||
"draft": False,
|
||||
"name": tag,
|
||||
"prerelease": False,
|
||||
"tag_name": tag,
|
||||
}))
|
||||
PY
|
||||
})"
|
||||
|
||||
lookup_response="$(
|
||||
curl --silent --show-error \
|
||||
--header "$authorization_header" \
|
||||
--output - \
|
||||
--write-out $'\n%{http_code}' \
|
||||
"$api_base/tags/$GITEA_REF_NAME"
|
||||
)"
|
||||
lookup_status="${lookup_response##*$'\n'}"
|
||||
lookup_body="${lookup_response%$'\n'*}"
|
||||
|
||||
case "$lookup_status" in
|
||||
200)
|
||||
release_id="$(python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' <<<"$lookup_body")"
|
||||
curl --fail-with-body --silent --show-error \
|
||||
--request PATCH \
|
||||
--header "$authorization_header" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "$release_payload" \
|
||||
"$api_base/$release_id" >/dev/null
|
||||
;;
|
||||
404)
|
||||
release_response="$(
|
||||
curl --fail-with-body --silent --show-error \
|
||||
--request POST \
|
||||
--header "$authorization_header" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data "$release_payload" \
|
||||
"$api_base"
|
||||
)"
|
||||
release_id="$(python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' <<<"$release_response")"
|
||||
;;
|
||||
*)
|
||||
echo "Could not look up Gitea release for $GITEA_REF_NAME (HTTP $lookup_status)." >&2
|
||||
echo "$lookup_body" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
asset_id="$({
|
||||
ASSET_NAME="$asset_name" python3 -c '
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
assets = json.load(sys.stdin)
|
||||
match = next((asset for asset in assets if asset["name"] == os.environ["ASSET_NAME"]), None)
|
||||
print(match["id"] if match else "")
|
||||
' < <(
|
||||
curl --fail-with-body --silent --show-error \
|
||||
--header "$authorization_header" \
|
||||
"$api_base/$release_id/assets"
|
||||
)
|
||||
})"
|
||||
|
||||
if [[ -n "$asset_id" ]]; then
|
||||
curl --fail-with-body --silent --show-error \
|
||||
--request DELETE \
|
||||
--header "$authorization_header" \
|
||||
"$api_base/$release_id/assets/$asset_id" >/dev/null
|
||||
fi
|
||||
|
||||
curl --fail-with-body --silent --show-error \
|
||||
--request POST \
|
||||
--header "$authorization_header" \
|
||||
--form "attachment=@${archive_path};filename=${asset_name}" \
|
||||
"$api_base/$release_id/assets?name=$asset_name" >/dev/null
|
||||
|
||||
echo "Published $asset_name to Gitea release $GITEA_REF_NAME."
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate the local repository structure used to package the integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parent.parent
|
||||
CUSTOM_COMPONENTS = REPOSITORY_ROOT / "custom_components"
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"Could not read valid JSON from {path.relative_to(REPOSITORY_ROOT)}: {error}") from error
|
||||
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"Expected a JSON object in {path.relative_to(REPOSITORY_ROOT)}")
|
||||
return value
|
||||
|
||||
|
||||
def main() -> None:
|
||||
integration_directories = sorted(
|
||||
path for path in CUSTOM_COMPONENTS.iterdir() if path.is_dir() and path.name != "__pycache__"
|
||||
)
|
||||
if len(integration_directories) != 1:
|
||||
names = ", ".join(path.name for path in integration_directories) or "none"
|
||||
raise ValueError(f"Expected exactly one integration under custom_components; found: {names}")
|
||||
|
||||
integration_directory = integration_directories[0]
|
||||
manifest = load_json(integration_directory / "manifest.json")
|
||||
required_manifest_fields = {
|
||||
"codeowners",
|
||||
"documentation",
|
||||
"domain",
|
||||
"issue_tracker",
|
||||
"name",
|
||||
"version",
|
||||
}
|
||||
missing_fields = sorted(required_manifest_fields - manifest.keys())
|
||||
if missing_fields:
|
||||
raise ValueError(f"manifest.json is missing required fields: {', '.join(missing_fields)}")
|
||||
if manifest["domain"] != integration_directory.name:
|
||||
raise ValueError("manifest.json domain must match its custom_components directory")
|
||||
if not isinstance(manifest["codeowners"], list) or not manifest["codeowners"]:
|
||||
raise ValueError("manifest.json codeowners must be a non-empty list")
|
||||
if not (integration_directory / "brand" / "icon.png").is_file():
|
||||
raise ValueError("The integration must include brand/icon.png")
|
||||
|
||||
hacs_manifest = load_json(REPOSITORY_ROOT / "hacs.json")
|
||||
if not isinstance(hacs_manifest.get("name"), str) or not hacs_manifest["name"].strip():
|
||||
raise ValueError("hacs.json must include a non-empty name")
|
||||
if hacs_manifest.get("zip_release") is True:
|
||||
filename = hacs_manifest.get("filename")
|
||||
if not isinstance(filename, str) or not filename.endswith(".zip"):
|
||||
raise ValueError("hacs.json zip_release requires a .zip filename")
|
||||
|
||||
if not (REPOSITORY_ROOT / "README.md").is_file():
|
||||
raise ValueError("The repository must include README.md")
|
||||
|
||||
print(f"Repository structure is valid for integration {manifest['domain']} {manifest['version']}.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user