feat(home-assistant): add ha-sourdough custom component #8

Open
finn.markwitz wants to merge 4 commits from feat/ha-sourdough into main
Showing only changes of commit 7bfbf4de9a - Show all commits
@@ -0,0 +1,152 @@
# Amazon Alexa integration for Home Assistant (jupiter) — design
**Date:** 2026-07-29
**Machine:** jupiter
**Status:** approved, ready for implementation plan
## Goal
Let Home Assistant control and read the household Amazon Echo devices via the
unofficial [`alexa_media_player`](https://github.com/Alandtse/alexa_media_player)
integration: text-to-speech / announcements, media control, and per-device
sensors (last-called device, next alarm/timer, DND state, etc.).
This uses the user's Amazon account through an unofficial API. It is a
config-flow integration: after the rebuild it is added through the HA UI, not
via YAML.
## Non-goals (deferred)
Alexa → HA voice control ("Alexa, turn on the light") is **out of scope**. It
would need either `emulated_hue` bound to port 80 on the LAN, or a public HTTPS
endpoint plus the AWS-Lambda Smart Home Skill. The user chose to decide on that
later. Nothing in this change touches the firewall, port 80, systemd unit
capabilities, or network exposure.
## Approach
Package the integration declaratively, following the existing `ha-sourdough`
pattern in `modules/environments/home-assistant/default.nix` (a
`buildHomeAssistantComponent` entry in `services.home-assistant.customComponents`).
No HACS runtime.
Unlike sourdough, `alexa_media_player` has real Python dependencies. Its
`manifest.json` (v5.15.7) declares:
```
alexapy==1.29.25
packaging>=20.3
wrapt>=1.14.0
dictor>=0.1.12,<0.2
```
`buildHomeAssistantComponent` runs `manifestCheckPhase` at build time
(`check_manifest.py`): every requirement must resolve to an installed
distribution **whose version satisfies the specifier**, or the build fails.
Home Assistant repeats this check at runtime. Therefore each requirement must be
satisfied exactly — the exact `==1.29.25` pin in particular.
Dependency status in the pinned nixpkgs (`nixos-25.11`):
| Requirement | nixpkgs today | Action |
| ---------------------- | ------------- | --------------------------------------- |
| `alexapy==1.29.25` | 1.29.22 | **Bump** to 1.29.25 via override |
| `dictor>=0.1.12,<0.2` | *absent* | **Package** dictor 0.1.12 (new) |
| `wrapt>=1.14.0` | 1.17.2 | none — already satisfied |
| `packaging>=20.3` | 26.1 | none — already satisfied |
`authcaptureproxy` (in nixpkgs at 1.3.7) is a transitive dependency of
`alexapy`, not listed in the manifest, so it needs no direct handling.
## Components
All changes live in `modules/environments/home-assistant/default.nix` (plus the
hashes below). Three small pieces, wired together in the module's `let` block:
### 1. `dictor` package (new)
Not in nixpkgs. Pure-Python, no runtime dependencies (`requires_dist: null`),
ships a `setup.py`. A minimal `pkgs.python3Packages.buildPythonPackage`:
- pname `dictor`, version `0.1.12`
- `src = fetchPypi { pname = "dictor"; version = "0.1.12"; hash = "sha256-bbSDda4eU9ye2EToWzj04/v79qTmC+yjd1Fa0URTuRs="; }`
- setuptools format (legacy `setup.py`); `build-system = [ setuptools ]`
- `doCheck = false` (no meaningful test suite); `pythonImportsCheck = [ "dictor" ]`
Build against the HA Python set so the version lands in HA's venv — i.e. use
`config.services.home-assistant.package.python.pkgs` (the same interpreter the
component check and HA runtime use), not the top-level `pkgs.python3Packages`.
### 2. `alexapy` 1.29.25 (override)
nixpkgs `alexapy` is fetched from **GitLab** (`keatontaylor/alexapy`, tag
`v<version>`), not PyPI. Override just the version + src via
`overridePythonAttrs`, reusing the existing build-system and dependency list:
- `version = "1.29.25"`
- `src = fetchFromGitLab { owner = "keatontaylor"; repo = "alexapy"; tag = "v1.29.25"; hash = "sha256-P/hvgqZVaBJF5dbmHrDjQMC+pwV3EEhKyFIS5KmhgD4="; }`
This is a patch bump (1.29.22 → 1.29.25); the dependency set is expected to be
unchanged. Override the HA-Python-set `alexapy` so it shares the interpreter
with dictor and the component.
### 3. `alexa_media_player` component (new `customComponents` entry)
```
buildHomeAssistantComponent {
owner = "Alandtse";
domain = "alexa_media";
version = "5.15.7";
src = fetchFromGitHub {
owner = "Alandtse";
repo = "alexa_media_player";
rev = "v5.15.7";
hash = "sha256-1rcZVSX1xA1Lc4qSu39MOitVEciZFhoPQy2y5+PpoAI=";
};
dependencies = [ alexapy' dictor' wrapt packaging ]; # HA-python-set packages
}
```
`dependencies` must make every manifest requirement importable at the required
version for `manifestCheckPhase` to pass. Include all four (the two custom ones
plus `wrapt` and `packaging` from the HA Python set) to be explicit.
## Data flow
1. `nixos-rebuild switch` builds the component; `manifestCheckPhase` validates
the four requirements against the provided `dependencies`.
2. HA starts; the custom component is present but not configured.
3. User adds **Alexa Media Player** in Settings → Devices & Services, signs in
with the Amazon account (email/password; 2FA or app-password handled in the
UI flow at runtime).
4. HA creates media_player entities and per-device sensors; TTS/announce
services become available for automations.
## Error handling / risks
- **Version-pin drift.** If a future `alexa_media_player` bump changes the
`alexapy==` pin, `alexapy` must be re-bumped in lockstep, or the build fails
loudly at `manifestCheckPhase` (fail-safe, not silent).
- **Amazon login fragility.** The unofficial API can break on Amazon's side
(captcha/2FA changes). This is a runtime concern, independent of packaging;
not addressed here.
- **`dictor` upper bound `<0.2`.** 0.1.12 is the current release and satisfies
it. If nixpkgs later gains a `dictor` ≥ 0.2, prefer our pinned 0.1.12 for
this component.
## Verification
- `nix build '.#nixosConfigurations.jupiter.config.system.build.toplevel'`
succeeds — this exercises the build-time manifest-requirements check for all
three new/overridden derivations.
- `nixfmt-rfc-style` clean on the edited module.
- Post-deploy (manual, by the user): the integration appears under Add
Integration, and a TTS/announce call reaches an Echo.
## Pinned artifacts
| Artifact | Source | Ref / version | Hash |
| --------------------------- | ---------- | ------------- | ------------------------------------------------- |
| alexa_media_player | GitHub | v5.15.7 | sha256-1rcZVSX1xA1Lc4qSu39MOitVEciZFhoPQy2y5+PpoAI= |
| alexapy | GitLab | v1.29.25 | sha256-P/hvgqZVaBJF5dbmHrDjQMC+pwV3EEhKyFIS5KmhgD4= |
| dictor | PyPI sdist | 0.1.12 | sha256-bbSDda4eU9ye2EToWzj04/v79qTmC+yjd1Fa0URTuRs= |