funoteka 0.1.1 → 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 CHANGED
@@ -9,6 +9,48 @@ 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
+
38
+ ## [0.1.2] — 2026-09-17
39
+
40
+ ### Fixed
41
+
42
+ - **The version the server reports is now the version it is.** `0.1.1`'s image and
43
+ package both answered `0.1.0` on `/health` and in every Subsonic envelope,
44
+ because the version lives in a constant (`SERVER_VERSION`) that a test pins to
45
+ `package.json` — and the bump to `0.1.1` raised the manifest and left the
46
+ constant behind. **That test failed, and `0.1.1` was tagged while CI was red**,
47
+ which is the more serious half of this: the pipeline was watching the artefacts
48
+ and nobody was watching the pipeline. The constant is corrected, and the release
49
+ job now fails when the image it pulls answers with a version other than the one
50
+ the release is about — a check that can fail is what would have caught it.
51
+ - Nothing else changed: the same program as `0.1.0` and `0.1.1`, and the same
52
+ schema.
53
+
12
54
  ## [0.1.1] — 2026-09-17
13
55
 
14
56
  ### 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.1`, `linux/amd64` and `linux/arm64` in one
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=<the token you generated> \
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.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=<the token you generated>
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 a config file from the example, and restricts its
224
- ACL. **Edit that config file before starting the service** it needs a password
225
- and an admin token. The script's own help says it has not been run end to end by
226
- whoever wrote it, and that is worth heeding: the first run is the verification.
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. Enrichment is possible later, and it is optional.
19
- - **An agent installs, configures and runs it.** Hand your agent `DEPLOY.md`.
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, with no build step at all. The published *package* is the one
80
- place that cannot hold: Node refuses to strip types from anything under `node_modules`
81
- (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`, and no flag lifts it), so what npm gets is the same
82
- sources compiled once at release (`npm run build`) a build that exists for the package and not for
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
 
@@ -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.0';
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.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",