Public Access
The jail can't reach Music Assistant's port 8095. MA_URL replaces the host of Music Assistant's image URLs when downloading. fetch-covers now stops at the first connection failure instead of stalling on every album, and image connections time out after 3 seconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148 lines
6.4 KiB
Markdown
148 lines
6.4 KiB
Markdown
# Tag albums
|
|
|
|
Tells Home Assistant which album to play when an RFID card (a **Tag**) is scanned, and provides a web page to give new cards an album (an **Assignment**). See [CONTEXT.md](CONTEXT.md) for the vocabulary.
|
|
|
|
How it fits together:
|
|
|
|
1. The ESPHome RFID reader sends `tag_scanned` to Home Assistant, as before.
|
|
2. HA's `Play album by tag` script calls `POST /api/scans`. The service records the **Scan** and answers with the album, or `assigned: false`.
|
|
3. A card the service hasn't seen before becomes an **Unassigned tag**. It appears on the web page within a few seconds, where you search the Music Assistant library and assign an album.
|
|
4. When a card is removed, HA's `Stop album by tag` script calls `GET /api/tags/{tag_id}`. It stops the player only if that tag has an album.
|
|
|
|
## Demo
|
|
|
|
A card is scanned while the page is open, shows up under Unassigned tags, and gets an album from a live search of the Music Assistant library:
|
|
|
|

|
|
|
|
## API
|
|
|
|
Both endpoints need `Authorization: Bearer <API_TOKEN>`.
|
|
|
|
| Endpoint | Purpose |
|
|
|---|---|
|
|
| `POST /api/scans` `{"tag_id": "53-13-0B-2A"}` | Record a Scan; creates the Tag on first sight. Returns `{"tag_id", "assigned": true, "artist", "album"}` or `{"tag_id", "assigned": false}`. |
|
|
| `GET /api/tags/{tag_id}` | Read-only lookup, same response. `404` if the tag has never been seen. |
|
|
|
|
Tag IDs are normalised to upper case.
|
|
|
|
The web page (`/`) has no login: keep the service on the LAN.
|
|
|
|
## Configuration
|
|
|
|
Environment variables:
|
|
|
|
| Variable | Meaning |
|
|
|---|---|
|
|
| `API_TOKEN` | Shared secret Home Assistant sends in the `Authorization` header |
|
|
| `HA_URL` | Home Assistant base URL, e.g. `https://ha.home.knbg` |
|
|
| `HA_TOKEN` | HA long-lived access token, used to search the Music Assistant library |
|
|
| `DB_PATH` | SQLite file (default `tag_albums.sqlite3`); its directory must be writable |
|
|
| `MA_URL` | Optional. Address to download Music Assistant cover images from, e.g. `https://ma.home.knbg`, when Music Assistant's own address (`http://<host>:8095`) isn't reachable from the service. See [Album covers](#album-covers). |
|
|
|
|
HTTPS to HA is verified against the system trust store, so a home CA must be installed on the host. If it isn't, point `SSL_CERT_FILE` at the CA bundle.
|
|
|
|
## Development
|
|
|
|
```sh
|
|
uv sync
|
|
uv run pytest
|
|
API_TOKEN=dev HA_URL=https://ha.home.knbg HA_TOKEN=... uv run tag-albums serve --port 8087
|
|
```
|
|
|
|
## Running with Docker
|
|
|
|
For local testing. The container has to trust the home CA to reach HA, so mount the CA certificate and point `SSL_CERT_FILE` at it:
|
|
|
|
```sh
|
|
docker build -t tag-albums .
|
|
docker run --rm -p 8087:8087 -v tag-albums-data:/data \
|
|
-e API_TOKEN=dev -e HA_URL=https://ha.home.knbg -e HA_TOKEN=... \
|
|
-v /usr/local/share/ca-certificates/KNBG_Root_CA_306526670621344964268144771797781855543.crt:/ca.crt:ro \
|
|
-e SSL_CERT_FILE=/ca.crt \
|
|
tag-albums
|
|
```
|
|
|
|
To import assignments, run `tag-albums import-yaml` inside the container, e.g. `docker run --rm -v tag-albums-data:/data -v $PWD/tag_albums.yaml:/import.yaml:ro tag-albums tag-albums import-yaml /import.yaml`.
|
|
|
|
## Deploying in a FreeBSD jail
|
|
|
|
```sh
|
|
pkg install python313 py313-sqlite3 py313-uv git rust # any Python >= 3.11; sqlite3 is a separate package
|
|
# uv compiles pydantic-core (Rust) and a few C extensions from source, since
|
|
# FreeBSD has no prebuilt wheels. The jail needs the base system's development
|
|
# files for that (e.g. /usr/lib/Scrt1.o, crti.o, libc): on a pkgbase jail,
|
|
# install the FreeBSD-*-dev packages (or the devel set) from the FreeBSD-base repo.
|
|
pw useradd tagalbums -d /nonexistent -s /usr/sbin/nologin
|
|
git clone <repo> /usr/local/tag-albums && cd /usr/local/tag-albums && uv sync --no-dev
|
|
install -d -o tagalbums /var/db/tag_albums
|
|
|
|
cat > /usr/local/etc/tag_albums.env <<'EOF'
|
|
API_TOKEN=<random, e.g. openssl rand -hex 32>
|
|
HA_URL=https://ha.home.knbg
|
|
HA_TOKEN=<HA long-lived token>
|
|
DB_PATH=/var/db/tag_albums/tag_albums.sqlite3
|
|
EOF
|
|
chmod 600 /usr/local/etc/tag_albums.env
|
|
|
|
install -m 755 deploy/rc.d/tag_albums /usr/local/etc/rc.d/tag_albums
|
|
sysrc tag_albums_enable=YES
|
|
service tag_albums start
|
|
```
|
|
|
|
Logs go to syslog with the tag `tag_albums`. The port defaults to 8087 (`sysrc tag_albums_port=...`).
|
|
|
|
### Importing the old tag_albums.yaml
|
|
|
|
One time, before switching Home Assistant over:
|
|
|
|
```sh
|
|
cd /usr/local/tag-albums
|
|
env DB_PATH=/var/db/tag_albums/tag_albums.sqlite3 \
|
|
su -m tagalbums -c '.venv/bin/tag-albums import-yaml /path/to/tag_albums.yaml'
|
|
```
|
|
|
|
### Album covers
|
|
|
|
The service stores each assignment's cover and serves it itself. Music Assistant's image URLs are plain http on another host, which browsers block on an https page. Albums assigned from the web page get their cover right away. Imported assignments have none, so fetch them once (this reads the env file for HA access):
|
|
|
|
```sh
|
|
cd /usr/local/tag-albums
|
|
su -m tagalbums -c 'set -a; . /usr/local/etc/tag_albums.env; set +a; .venv/bin/tag-albums fetch-covers'
|
|
```
|
|
|
|
It lists the albums Music Assistant has no cover for; those keep the grey placeholder. Running it again only looks at assignments still missing a cover.
|
|
|
|
The service downloads covers from the address Music Assistant reports: its own host and port 8095, over plain http. If the jail can't reach that, put Music Assistant's image proxy behind the reverse proxy and set `MA_URL` to it. For example, in Caddy (use the same TLS setup as your other sites):
|
|
|
|
```caddyfile
|
|
ma.home.knbg {
|
|
@images path /imageproxy/*
|
|
handle @images {
|
|
reverse_proxy 172.16.40.250:8095
|
|
}
|
|
respond 404
|
|
}
|
|
```
|
|
|
|
Then add `MA_URL=https://ma.home.knbg` to the env file and restart the service. Only `/imageproxy/*` is exposed, not the Music Assistant interface. If `fetch-covers` can't connect, it stops at the first cover and says so.
|
|
|
|
### Updating
|
|
|
|
```sh
|
|
cd /usr/local/tag-albums && git pull && uv sync --no-dev
|
|
service tag_albums restart
|
|
```
|
|
|
|
Database changes are applied automatically at startup.
|
|
|
|
## Home Assistant side
|
|
|
|
These pieces live in the `home-assistant-config` repo:
|
|
|
|
- `configuration.yaml`: two `rest_command`s, `tag_albums_scan` and `tag_albums_lookup`, pointing at `https://tag-albums.home.knbg` (a reverse proxy in front of port 8087). They set `verify_ssl: false`, because the certificate comes from the home CA and HA only trusts its built-in list of public CAs.
|
|
- `secrets.yaml`: `tag_albums_auth: "Bearer <API_TOKEN>"`.
|
|
- `scripts.yaml`: `play_album_by_tag` and `stop_album_by_tag` call those commands.
|
|
|
|
If the service is down, a scan plays nothing and `play_album_by_tag` writes a warning to the HA log.
|