Scaffold enligt new-project-playbook: README, plan, CI-workflow, build-task
Some checks failed
build-and-push / build (push) Failing after 46s
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:
38
.gitea/workflows/build.yaml
Normal file
38
.gitea/workflows/build.yaml
Normal 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
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
build/
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.sqlite3
|
||||||
|
data/
|
||||||
|
coverage/
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
12
.vscode/tasks.json
vendored
Normal file
12
.vscode/tasks.json
vendored
Normal 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
47
README.md
Normal 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
80
doc/plan.md
Normal 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
|
||||||
Reference in New Issue
Block a user