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