funoteka 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/DEPLOY.md +55 -8
- package/README.md +32 -7
- package/dist/api/envelope.js +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,32 @@ The version lives in exactly one place — `package.json`.
|
|
|
9
9
|
|
|
10
10
|
_(nothing yet)_
|
|
11
11
|
|
|
12
|
+
## [0.1.3] — 2026-09-17
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **The public README, which is a release artefact and ships in this package.**
|
|
17
|
+
Three things in it are new to a reader: **what it costs**, measured rather than
|
|
18
|
+
asserted (about 42 MB of RAM idle, 30 MB of SQLite for 3 432 songs, a 421 MB
|
|
19
|
+
image, and a collection that is only ever read); the **first scan is a step in
|
|
20
|
+
the quick start** (it was missing, so the documented install produced a server
|
|
21
|
+
with an empty library); and the **agent pitch opens the file**. The clients named
|
|
22
|
+
are the ones that have actually connected (`player`: Feishin, Symfonium,
|
|
23
|
+
Substreamer, Amcfy Music, Castafiore), not a compatibility wish-list.
|
|
24
|
+
- **A checkout now has a working `funoteka`.** `bin` names `dist/cli.js` and a
|
|
25
|
+
clone had no `dist/` — so `npm install && npm link` produced a binary that
|
|
26
|
+
failed — until `prepare` was added to build it on install.
|
|
27
|
+
- `DEPLOY.md` gained the two journeys that were guesses: the Windows service
|
|
28
|
+
wrapper versus the daemon that is in daily use, and the two things a NAS shows
|
|
29
|
+
that the command does not (the container's uid, and the arm64 half being built
|
|
30
|
+
under emulation).
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- The engine of this patch is unchanged: no server code, no schema. What changed
|
|
35
|
+
is what a reader is told, and one line of `package.json` that makes the clone
|
|
36
|
+
behave.
|
|
37
|
+
|
|
12
38
|
## [0.1.2] — 2026-09-17
|
|
13
39
|
|
|
14
40
|
### Fixed
|
package/DEPLOY.md
CHANGED
|
@@ -61,7 +61,7 @@ node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
|
|
|
61
61
|
## 3. Path A — Docker (main path)
|
|
62
62
|
|
|
63
63
|
Works the same on a laptop, a NAS and a VDS. There is a **published image** —
|
|
64
|
-
`ghcr.io/kzntsv-dev/funoteka:0.1.
|
|
64
|
+
`ghcr.io/kzntsv-dev/funoteka:0.1.3`, `linux/amd64` and `linux/arm64` in one
|
|
65
65
|
manifest — and `build: .` still builds on the machine that runs it (that is the
|
|
66
66
|
path where a published image is not wanted or not reachable).
|
|
67
67
|
|
|
@@ -72,16 +72,32 @@ installation (verified by running exactly this against the published image):
|
|
|
72
72
|
MUSIC=/absolute/path/to/your/music
|
|
73
73
|
docker run -d --name funoteka -p 4533:4533 -p 4534:4534 \
|
|
74
74
|
-e FUNOTEKA_USER=you -e FUNOTEKA_PASSWORD=change-me \
|
|
75
|
-
-e FUNOTEKA_ADMIN_TOKEN
|
|
75
|
+
-e FUNOTEKA_ADMIN_TOKEN=PASTE_THE_TOKEN_YOU_GENERATED \
|
|
76
76
|
-e FUNOTEKA_SUPERVISED=1 -e FUNOTEKA_LOG_FILE=/data/funoteka.log \
|
|
77
77
|
-v "$MUSIC":/music:ro -v funoteka-data:/data \
|
|
78
|
-
--restart unless-stopped ghcr.io/kzntsv-dev/funoteka:0.1.
|
|
78
|
+
--restart unless-stopped ghcr.io/kzntsv-dev/funoteka:0.1.3
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
(The token above is a placeholder with no angle brackets in it, on purpose: a
|
|
82
|
+
copy-paste that misses the substitution should fail to authenticate loudly rather
|
|
83
|
+
than carry a string that looks like a token.)
|
|
84
|
+
|
|
81
85
|
`FUNOTEKA_SUPERVISED=1` is what makes `POST /restart` a restart rather than a
|
|
82
86
|
stop, and the restart policy beside it is what supervises the process. The first
|
|
83
87
|
scan is still a command, not a startup side effect — see the end of this section.
|
|
84
88
|
|
|
89
|
+
Two things that bite on a NAS, and neither is visible from the command above:
|
|
90
|
+
|
|
91
|
+
- **The container runs as `node`, uid 1000.** A collection on a share that this
|
|
92
|
+
uid cannot read scans as an empty root — the server says so rather than failing
|
|
93
|
+
(`nothing here — an empty directory and a mistyped path read alike`), and the
|
|
94
|
+
fix is on the host: `chown` it, grant the group, or run the container with
|
|
95
|
+
`--user` set to the owner.
|
|
96
|
+
- **The `arm64` half of the published image is built under emulation** on an
|
|
97
|
+
`amd64` machine. It runs correctly and it is not as fast as a build made on
|
|
98
|
+
that box would be — for a small board this is the difference worth measuring,
|
|
99
|
+
and `build: .` in a checkout on that box is the other path.
|
|
100
|
+
|
|
85
101
|
From a checkout, the same installation is the compose file:
|
|
86
102
|
|
|
87
103
|
```sh
|
|
@@ -97,7 +113,7 @@ MUSIC=/absolute/path/to/your/music # read-only into the container
|
|
|
97
113
|
DATA=./data # everything the server owns
|
|
98
114
|
FUNOTEKA_USER=you
|
|
99
115
|
FUNOTEKA_PASSWORD=change-me
|
|
100
|
-
FUNOTEKA_ADMIN_TOKEN
|
|
116
|
+
FUNOTEKA_ADMIN_TOKEN=PASTE_THE_TOKEN_YOU_GENERATED
|
|
101
117
|
```
|
|
102
118
|
|
|
103
119
|
Start it:
|
|
@@ -214,16 +230,21 @@ admin calls as §3 — add the root with the path **as this machine sees it**
|
|
|
214
230
|
|
|
215
231
|
## 5. Path C — native Windows (service)
|
|
216
232
|
|
|
233
|
+
**The daemon works on Windows and is in daily use** — it is how the author's own
|
|
234
|
+
server runs (started by a `start.cmd` that carries the `FUNOTEKA_*` variables
|
|
235
|
+
from §3). What has not been run end to end is the *service wrapper* below, and
|
|
236
|
+
its own help says so.
|
|
237
|
+
|
|
217
238
|
```powershell
|
|
218
239
|
# elevated PowerShell, from the checkout
|
|
219
240
|
.\deploy\windows\install-service.ps1 -Data 'C:\ProgramData\funoteka'
|
|
220
241
|
```
|
|
221
242
|
|
|
222
243
|
The script downloads WinSW (pinned, hash-checked) to wrap `node src/cli.ts
|
|
223
|
-
serve` as a service, writes
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
244
|
+
serve` as a service, writes the config file to `<Data>\funoteka.json` and the log
|
|
245
|
+
to `<Data>\funoteka.log`, and restricts the directory's ACL. **Edit that config
|
|
246
|
+
file before starting the service** — it needs a password and an admin token, and
|
|
247
|
+
the service reads them from there rather than from a shell.
|
|
227
248
|
|
|
228
249
|
## 6. Verify, then keep it running
|
|
229
250
|
|
|
@@ -350,6 +371,9 @@ so inside a container the script is named instead.
|
|
|
350
371
|
# a native install, with the bin on PATH (`npm link`, or `npm install -g .`)
|
|
351
372
|
FUNOTEKA_ADMIN_TOKEN=$TOKEN funoteka mcp
|
|
352
373
|
|
|
374
|
+
# or without installing anything: the published package, the same command
|
|
375
|
+
FUNOTEKA_ADMIN_TOKEN=$TOKEN npx -y funoteka mcp
|
|
376
|
+
|
|
353
377
|
# the container — the token arrives through .env, so it need not be repeated
|
|
354
378
|
docker compose exec -T funoteka node src/cli.ts mcp
|
|
355
379
|
```
|
|
@@ -393,3 +417,26 @@ own status is in `GET /scan/history`.
|
|
|
393
417
|
with them), then whether the song exists (`/rest/search3`), then the log with
|
|
394
418
|
`FUNOTEKA_LOG_REQUESTS=1`, which prints one line per request with the method and
|
|
395
419
|
the query — masked, because a client spells its password there.
|
|
420
|
+
|
|
421
|
+
## 13. Why the published package is built, and the repository is not
|
|
422
|
+
|
|
423
|
+
The repository runs its own TypeScript: `node src/cli.ts` is the program, `npm
|
|
424
|
+
test` is the suite, and there is no build step to run before either of them.
|
|
425
|
+
|
|
426
|
+
**The npm package cannot do that**, and the reason is a rule rather than a
|
|
427
|
+
preference: Node refuses to strip types from any file under `node_modules`
|
|
428
|
+
(`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`), and no flag lifts it — checked on
|
|
429
|
+
v24.19.0 with the default, both `--experimental-strip-types` and
|
|
430
|
+
`--experimental-transform-types`, and the two together. A `bin` that named a `.ts`
|
|
431
|
+
file would install a package that cannot start.
|
|
432
|
+
|
|
433
|
+
So `npm run build` compiles `src` into `dist` for the package only
|
|
434
|
+
(`tsconfig.build.json`, `deploy/build.mjs`), copying the schema migrations
|
|
435
|
+
beside the code that reads them, and `bin` names `dist/cli.js`. Two consequences
|
|
436
|
+
worth knowing:
|
|
437
|
+
|
|
438
|
+
- **A fresh checkout has no `dist/`** and therefore no `funoteka` bin until the
|
|
439
|
+
build runs. `npm install` in a checkout now runs it (`prepare`), so
|
|
440
|
+
`npm link` from a clone works; `git clone` without an install does not.
|
|
441
|
+
- **The image still copies `src/`** and runs `node src/cli.ts serve` — the
|
|
442
|
+
container is a checkout, and it never needs the build.
|
package/README.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
**The Subsonic server a real collection deserves.**
|
|
4
4
|
|
|
5
|
+
> ## Give `DEPLOY.md` to your agent.
|
|
6
|
+
> Installing a music server means answering fiddly questions — where the music lives, which ports,
|
|
7
|
+
> TLS or a reverse proxy, where the database goes, who may log in. Hand them to an agent: it reads
|
|
8
|
+
> `DEPLOY.md`, asks you only what matters, installs funoteka (**Docker or native** — your machine,
|
|
9
|
+
> your NAS, or a VDS), configures it, and then **keeps running it** — rescanning the library,
|
|
10
|
+
> filtering junk, managing roots — over the **admin API and MCP**.
|
|
11
|
+
|
|
5
12
|
**A folder is an album** — not a guess, not a heuristic: the rule. funoteka keys every record on
|
|
6
13
|
its folder path, so three pressings of the same album stay three albums, a box stays a box and its
|
|
7
14
|
discs, and an image file with a cue sheet becomes real, playable tracks. Nothing merged, nothing lost.
|
|
@@ -15,8 +22,12 @@ discs, and an image file with a cue sheet becomes real, playable tracks. Nothing
|
|
|
15
22
|
- **Your files are never modified.** Classification, cue boundaries, encodings, dedup — all of it
|
|
16
23
|
lives in a meta layer beside the server. Point it at your disk; your disk stays as it is.
|
|
17
24
|
- **No LLM, no Discogs, no Last.fm, no external service.** The core reads what is in your files and
|
|
18
|
-
folders
|
|
19
|
-
|
|
25
|
+
folders, and what it writes stays on your disk: the meta layer, the log, and an audit file beside
|
|
26
|
+
the database that records what was changed through the admin surface. Enrichment is possible
|
|
27
|
+
later, and it is optional.
|
|
28
|
+
- **Any client you already like just works.** Symfonium, Feishin, Substreamer, Amcfy Music and
|
|
29
|
+
Castafiore have all reported themselves to this server, and none of them needed anything special;
|
|
30
|
+
the API is the point, the client is yours.
|
|
20
31
|
|
|
21
32
|
A complete **Subsonic / OpenSubsonic** surface, filled from your own files wherever it can be and
|
|
22
33
|
answered honestly and empty wherever it cannot — so the client you already like just works.
|
|
@@ -50,6 +61,18 @@ Three albums, a box set of twelve discs. No merging, no guessing, nothing lost.
|
|
|
50
61
|
|
|
51
62
|
## Install
|
|
52
63
|
|
|
64
|
+
### What it costs
|
|
65
|
+
|
|
66
|
+
Measured on the machine this was built on, and worth knowing before you point it at 200 GB of FLAC:
|
|
67
|
+
|
|
68
|
+
| | |
|
|
69
|
+
|---|---|
|
|
70
|
+
| **Idle** | about **42 MB** of RAM, no measurable CPU — the container, with an empty library |
|
|
71
|
+
| **The meta layer** | **30 MB** of SQLite for 3 432 songs and 472 albums (the author's own library); it grows with the catalogue, not with the audio |
|
|
72
|
+
| **The re-encode cache** | grows with what you ask for: 300 MB after a year of cue segments from m4a/MP4 images, and it is inside the one directory you mount |
|
|
73
|
+
| **The collection** | read-only, never written to, never copied anywhere. The server reads the bytes it serves and does not keep the library in memory |
|
|
74
|
+
| **The image** | 421 MB, ffmpeg included |
|
|
75
|
+
|
|
53
76
|
### Docker — the main path
|
|
54
77
|
|
|
55
78
|
The image carries everything, **ffmpeg included** — the only external binary the server ever wants,
|
|
@@ -76,11 +99,10 @@ docker compose exec funoteka node src/cli.ts scan /music # fill the library, o
|
|
|
76
99
|
### Native, without Docker
|
|
77
100
|
|
|
78
101
|
Needs **Node 24+**. There are **no runtime dependencies** — Node carries SQLite and FTS5, and the
|
|
79
|
-
repository runs its own TypeScript
|
|
80
|
-
place that cannot hold
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
the repository. `ffmpeg` on the PATH is needed only for cue tracks inside an m4a/MP4 container.
|
|
102
|
+
repository runs its own TypeScript with no build step at all. (The published *package* is the one
|
|
103
|
+
place that cannot hold, because Node refuses to strip types from anything under `node_modules`; what
|
|
104
|
+
npm gets is the same sources compiled once at release. Why, and what that changes — `DEPLOY.md` §13.)
|
|
105
|
+
`ffmpeg` on the PATH is needed only for cue tracks inside an m4a/MP4 container.
|
|
84
106
|
|
|
85
107
|
```sh
|
|
86
108
|
npm install -g funoteka
|
|
@@ -132,6 +154,9 @@ classification and you edit that layer, not your music.
|
|
|
132
154
|
|
|
133
155
|
- `DEPLOY.md` — install, configure and manage, written to be read by an agent
|
|
134
156
|
- The admin API and the MCP server — `DEPLOY.md` §11
|
|
157
|
+
- Something broken, or missing: [an issue](https://github.com/kzntsv-dev/funoteka/issues) — say
|
|
158
|
+
what you did, what you expected and what the server said; the log and `GET /issues` usually
|
|
159
|
+
already contain the answer, and the troubleshooting table in `DEPLOY.md` has the rest
|
|
135
160
|
|
|
136
161
|
## License
|
|
137
162
|
|
package/dist/api/envelope.js
CHANGED
|
@@ -30,7 +30,7 @@ export const API_VERSION = '1.16.1';
|
|
|
30
30
|
* module every route depends on.
|
|
31
31
|
*/
|
|
32
32
|
export const SERVER_TYPE = 'funoteka';
|
|
33
|
-
export const SERVER_VERSION = '0.1.
|
|
33
|
+
export const SERVER_VERSION = '0.1.3';
|
|
34
34
|
/**
|
|
35
35
|
* The fields every answer carries, whatever the answer is.
|
|
36
36
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "funoteka",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Subsonic-compatible server: smart music library — scanner-classifier, cue splitting, meta layer, virtual tree",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "node deploy/build.mjs",
|
|
43
43
|
"prepublishOnly": "npm run build",
|
|
44
|
+
"prepare": "node deploy/build.mjs",
|
|
44
45
|
"scan": "node src/cli.ts scan",
|
|
45
46
|
"serve": "node src/cli.ts serve",
|
|
46
47
|
"smoke": "node deploy/smoke.mjs",
|