Scaffold enligt new-project-playbook: README, plan, CI-workflow, build-task
Some checks failed
build-and-push / build (push) Failing after 46s

lyssnarr — musik-discovery på Pi5. Jellyseerr-lik UI, sök (MusicBrainz +
YT Music), schemalagda Gemini-förslag från Lidarr-bibliotek + Youtubarr-
spellistor, add-to-Lidarr / spela inline / öppna i YT Music.
Plan i doc/plan.md — implementation börjar när planen är bekräftad.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01824ZrvG2mDYYrmwqypLNup
This commit is contained in:
2026-07-31 23:12:46 +02:00
commit f164a263ee
5 changed files with 187 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
name: build-and-push
# Bygger lyssnarr-imagen på Pi5-runnern (arm64, nativt) och pushar till den
# lokala registryn — samma mönster som Archivum. Multi-stage (frontend ->
# backend -> runtime), build-context är repo-roten. Pull:a på Pi5 med
# localhost:5000/lyssnarr:latest (dockge-stacken).
#
# OBS: bygget kör bredvid live-tjänsterna på Pi5 — nice/ionice + BUILD_CPUS=2
# så boxen förblir responsiv (max 2 tunga byggen samtidigt, se infra-Doc).
on:
push:
branches: [master]
paths:
- "backend/**"
- "frontend/**"
- "docker/**"
- "package.json"
- ".gitea/workflows/build.yaml"
workflow_dispatch: {}
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build & push image
run: |
nice -n 19 ionice -c 3 docker build --progress=plain \
--build-arg BUILD_CPUS=2 \
-f docker/Dockerfile \
-t localhost:5000/lyssnarr:latest \
-t "localhost:5000/lyssnarr:${GITHUB_SHA::12}" \
.
docker push localhost:5000/lyssnarr:latest
docker push "localhost:5000/lyssnarr:${GITHUB_SHA::12}"

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
build/
node_modules/
dist/
.env
.env.*
*.sqlite3
data/
coverage/
.DS_Store
*.log

12
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "npm run build",
"group": { "kind": "build", "isDefault": true },
"problemMatcher": []
}
]
}

47
README.md Normal file
View File

@@ -0,0 +1,47 @@
# lyssnarr
Self-hosted music discovery for the Pi5 — a Jellyseerr-style web UI that
finds new music for you. Search for artists/albums/tracks, get scheduled
AI-generated suggestions (Gemini) based on what you recently listened to
and what you add to your YouTube Music playlists, and add anything to
Lidarr with one click — or play it inline / open it in YouTube Music.
See [`doc/plan.md`](doc/plan.md) for the agreed plan and the
[Gitea wiki](https://gitea.brasse-pc.eu/brasse/lyssnarr/wiki) for
architecture notes.
## Prerequisites (dev box = Arch/Garuda)
```bash
sudo pacman -S nodejs npm # node >= 20
```
## Build & run
```bash
git clone gitea-claude:brasse/lyssnarr.git
cd lyssnarr
npm install # installs backend + frontend workspaces
npm run build # -> ./build/ (frontend dist + backend)
npm run dev # dev servers (Vite + Fastify, hot reload)
npm test
```
## Runtime configuration
All secrets live in the Dockge stack `.env` on the Pi5 — never in the
repo. Key variables (full list in `doc/plan.md`):
| Var | What |
|-----|------|
| `GEMINI_API_KEY` | Google AI Studio key for suggestion generation |
| `LIDARR_URL` / `LIDARR_API_KEY` | Lidarr instance to add music to |
| `SESSION_SECRET` | cookie signing |
| `APP_PASSWORD_HASH` | bcrypt hash for the single-user login |
## Deploy
CI (`.gitea/workflows/build.yaml`) builds the arm64 image on the Pi5
runner and pushes `localhost:5000/lyssnarr:latest`; the Dockge stack
`/srv/dockge-staks/lyssnarr/` runs it behind NPM as
`https://lyssnarr.brasse-pc.eu`.

80
doc/plan.md Normal file
View File

@@ -0,0 +1,80 @@
# lyssnarr — plan
## Goal
Self-hosted music-discovery web app on the Pi5. A Jellyseerr-style UI
where Björn can (a) search for new artists, albums and tracks, and
(b) get **scheduled** AI-generated suggestions based on two taste
signals: recent listening in the local library (Jellyfin) and tracks
recently added to his YouTube Music playlists (via Youtubarr's data).
Every result can be added to Lidarr with one click, opened in YouTube
Music, or played inline in the app.
## Stack
- **Backend:** Node ≥ 20, Fastify, better-sqlite3 (suggestion history +
feedback), node-cron (scheduled generation)
- **Frontend:** Vue 3 + Vite; dark, poster/card-based UI modelled on
Jellyseerr (top search bar, discover grid, action buttons per card);
built to static files served by the backend
- **Container:** single arm64 image, multi-stage Dockerfile
(frontend build → backend deps → slim runtime), mirrors Archivum
## Deploy target
Pattern B (container on Pi5):
Gitea Actions on the Pi5 runner → `localhost:5000/lyssnarr:latest`
Dockge stack `/srv/dockge-staks/lyssnarr/` → NPM vhost
`https://lyssnarr.brasse-pc.eu` (public from day one → the app ships
with login from day one: single user, bcrypt-hashed password from the
stack `.env`, signed session cookie).
## Integrations
| What | How |
|------|-----|
| **Lidarr** | REST API (`http://lidarr:8686`): add/monitor artists & albums, check what's already in the library |
| **MusicBrainz** | search + canonical MBIDs (Lidarr adds are MBID-based) |
| **YouTube Music** | unofficial YT Music search from Node → browse/video IDs for "open in YT Music" deep links, thumbnails for the cards, inline playback via YouTube IFrame embed |
| **Gemini API** | suggestion generation; `GEMINI_API_KEY` + model via env |
| **Youtubarr** | read-only mount of its SQLite (`/srv/docker/youtubarr/data/db.sqlite3`) → "recently added to playlists" signal |
| **Jellyfin** | play-history signal — blocked until a music library exists in Jellyfin (milestone 6) |
## UI sketch (Jellyseerr-like)
- Top: search bar with artist/album/track scope
- Landing "Upptäck": latest suggestion batch as poster cards, each with
Gemini's one-line motivation
- Card actions: ** Lidarr** · **▶ Spela** (inline YouTube embed) ·
**↗ YT Music** (deep link)
- Badge on cards already in the Lidarr library
- History page with earlier batches
## Milestones
1. **Skeleton end-to-end** — repo, CI, Dockerfile, dockge stack, NPM
vhost, login; an empty authenticated shell reachable on
`lyssnarr.brasse-pc.eu`
2. **Lidarr integration** — library state + add/monitor actions
3. **Search page** — MusicBrainz search enriched with YT Music
thumbnails/links, add/play/open actions
4. **On-demand suggestions** — "generera nu": Gemini prompt built from
Lidarr library + Youtubarr playlist adds
5. **Scheduler + history** — cron-generated batches, history page
6. **Jellyfin signal** — recent music plays feed the prompt (requires
music library in Jellyfin)
## Roadmap / expansions (proposed — Björn confirms/strikes)
- 👍/👎 feedback per suggestion, fed into the next Gemini prompt
- Auto-add top suggestions to Lidarr as monitored (configurable)
- Track-level batches exported as M3U playlists into Jellyfin
- Library profile page (genre/decade distribution)
- Webhook/notification when a new batch lands
- Liked Music as a direct signal (ytmusicapi sidecar or Node port)
## Open questions
- Gemini model: default `gemini-2.5-flash`, configurable via env — ok?
- Auth: built-in single-user login vs Authentik forward-auth later
- Artwork order: YT Music thumbnails first, Cover Art Archive fallback