Repository navigation
Releases
v2.2.2
WaveFlow Server 2.2.2
A fix release, for two problems reported together in #444. The first stopped the image at startup when the host provided its data folder, as Home Assistant does. The second stopped the desktop app's playback about a minute into a track. Nothing changes in the API or the database.
Fixed
-
The image starts on a data folder it did not create. The image used to start directly as its account (uid 4533). When the host provided the data folder and that folder belonged to root or to another account, the server stopped at once with unable to open database file. This happens with Home Assistant's add-on folder, NAS shares owned by another account, and bind mounts made by root. A share that refuses
chownaltogether (NFS with root squashed, some SMB mounts) gets a warning and the server starts anyway; it still needs the account to be able to write there.The container now starts as root and gives the data folder to the account. Only then does it start the server, as that account, so never as root unless you ask for it with
WAVEFLOW_UID=0.docker exec waveflow waveflow-server …runs as that account too, so a command such aslibrary addleaves no file the server cannot replace. If you used achown -R 4533:4533workaround, you no longer need it for a data folder the container hands over; one it leaves alone (see below) still has to be writable by the account.- Another account: set
WAVEFLOW_UIDandWAVEFLOW_GID(both 4533 by default).0keeps root, which suits rootless Podman. --user(oruser:in a compose file) still runs the server as the account you give, and skips the hand-over.- What is left alone: anything mounted inside the data folder, a hard link, and the target of a symbolic link keep their owner. A data folder set to
/, reached through a link or placed under a folder that is not root's alone is not handed over at all, and the log says why.
The operations guide has the details.
- Another account: set
-
Server playback in the desktop app no longer stops after a minute. The server answered every byte-range request with at most 8 MiB. HTTP allows a shorter answer, but the desktop app took its end for the end of the file. A FLAC streamed from the server therefore stopped after about a minute, and a high-resolution file sooner. An open range such as
bytes=N-now runs to the end of the file, and a bounded range to the end it asks for. This applies to the native stream and to the Subsonic API alike.
Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.2.2The image runs on linux/amd64 and linux/arm64, and is the same, digest for digest, on Docker Hub as instazdll/waveflow-server:2.2.2. latest now follows this release. The standalone archives for Windows, macOS and Linux are attached below, with SHA256SUMS over all of them. The same files are linked from this release's notes on download.waveflow.app, which hold whether or not GitHub answers.
Upgrading from 2.2.1
Pull 2.2.2 and start it on the same data volume. With an archive, use the new folder in place of the old one, carrying over what you changed in laptop.env. The database schema has not changed, so there is nothing to migrate.
With the default account, the first start gives the files it hands over (not those listed above as left alone) to uid and gid 4533. On a volume 2.2.1 wrote, that changes their group only: 2.2.1 let Debian assign that group number when its image was built, typically 999. A file that belonged to another account, a host user's in a bind mount for instance, changes owner as well, and that account may no longer be able to write to it. 2.2.1 still opens the volume should you need to go back, since it runs as the same uid. The hand-over changes owners, never modes: a folder or file that only its group could use (mode 0070, say) stays out of reach of both versions until you give its owner access with chmod u+rwX.
With WAVEFLOW_UID and WAVEFLOW_GID set to another account, the files handed over belong to that account instead, and 2.2.1, which runs as 4533, may not be able to write to them. Give them back to uid 4533 before you go back.
The image no longer declares a USER. It starts as root and steps down before the server runs. An image scanner that checks for a non-root default user will now report it.
A server on 2.2.1 with the update check turned on announces this release on its own.
Full changelog: CHANGELOG.md · v2.2.1...v2.2.2
Download
The server for one computer, without Docker: extract the archive and start waveflow-server inside it. Each archive holds the server, its own FFmpeg and a README.txt to read first.
| System | Archive |
|---|---|
| Windows (x86_64) | waveflow-server-2.2.2-windows-x86_64.zip |
| macOS (Apple silicon) | waveflow-server-2.2.2-macos-arm64.zip |
| macOS (Intel) | waveflow-server-2.2.2-macos-x86_64.zip |
| Linux (x86_64) | waveflow-server-2.2.2-linux-x86_64.tar.gz |
| Linux (arm64) | waveflow-server-2.2.2-linux-arm64.tar.gz |
The Windows executables carry a self-signed certificate and the macOS ones an ad-hoc signature, which neither system trusts, so both warn on the first launch: README.txt says what to click past. SHA256SUMS holds the checksums of them all, and ffmpeg-8.1.3-sources.tar.gz the source of the FFmpeg they carry.
Container image
docker pull ghcr.io/instazdll/waveflow-server:2.2.2The same image, digest for digest, is on Docker Hub:
docker pull instazdll/waveflow-server:2.2.2Also published as latest in both, for linux/amd64 and linux/arm64 (a Raspberry Pi 4 or 5 on a 64-bit system).
v2.2.1
WaveFlow Server 2.2.1
A maintenance release. The server now builds on the 2.0.0 release of the core it shares with the desktop app, and the Rust and web dependencies have been brought up to date. Two things change in how a scan reads covers. Nothing changes in the API or the database.
Fixed
-
The cover stored in a file is its front cover. When a file's tag holds several pictures, the scan used to take the first one. A back cover or a booklet page stored ahead of the front cover then became the track's cover, and the album's. The scan now takes the picture marked as the front cover, and falls back to the first picture only when there is none.
This applies to files the scan reads from now on: new files, and files that changed. A track already in your library keeps its cover until its file changes. To have every file read again, and every cover chosen again, run
waveflow-server database rescan-fullonce: in the container withdocker exec waveflow waveflow-server database rescan-full, so that it reaches the database under/data. The next scan of each library then reads every file, as the operations guide explains. -
A cover cut short is no longer kept. Covers are now written to a temporary file and only then given their final name. Before, a crash or a full disk during the write could leave a truncated image under that name, and later scans reused it as it was.
Unchanged on purpose
The core now reads WavPack (.wv) and Monkey's Audio (.ape) files, but this server still leaves them out, both from scans and from uploads. The FFmpeg that the images and the standalone archives ship cannot decode either format, so such a track would appear in your library and then fail to play. They will be added together with that support.
Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.2.1The image runs on linux/amd64 and linux/arm64, and is the same, digest for digest, on Docker Hub as instazdll/waveflow-server:2.2.1. latest now follows this release. The standalone archives for Windows, macOS and Linux are attached below, with SHA256SUMS over all of them. The same files are linked from this release's notes on download.waveflow.app, which hold whether or not GitHub answers.
Upgrading from 2.2.0
Pull 2.2.1 and start it on the same data volume. With an archive, use the new folder in place of the old one, carrying over what you changed in laptop.env. The database schema has not changed, so there is nothing to migrate, and 2.2.0 still opens the same volume should you need to go back.
A server on 2.2.0 with the update check turned on announces this release on its own.
Coming from 2.1.2 or earlier, read the 2.2.0 notes first: that upgrade adds a table, and going back from it takes a restore.
Full changelog: CHANGELOG.md · v2.2.0...v2.2.1
Download
The server for one computer, without Docker: extract the archive and start waveflow-server inside it. Each archive holds the server, its own FFmpeg and a README.txt to read first.
| System | Archive |
|---|---|
| Windows (x86_64) | waveflow-server-2.2.1-windows-x86_64.zip |
| macOS (Apple silicon) | waveflow-server-2.2.1-macos-arm64.zip |
| macOS (Intel) | waveflow-server-2.2.1-macos-x86_64.zip |
| Linux (x86_64) | waveflow-server-2.2.1-linux-x86_64.tar.gz |
| Linux (arm64) | waveflow-server-2.2.1-linux-arm64.tar.gz |
The Windows executables carry a self-signed certificate and the macOS ones an ad-hoc signature, which neither system trusts, so both warn on the first launch: README.txt says what to click past. SHA256SUMS holds the checksums of them all, and ffmpeg-8.1.3-sources.tar.gz the source of the FFmpeg they carry.
Container image
docker pull ghcr.io/instazdll/waveflow-server:2.2.1The same image, digest for digest, is on Docker Hub:
docker pull instazdll/waveflow-server:2.2.1Also published as latest in both, for linux/amd64 and linux/arm64 (a Raspberry Pi 4 or 5 on a 64-bit system).
v2.2.0
WaveFlow Server 2.2.0
The server can now tell its administrators when a new version is out, after asking them first. Every release also gains a second place to download it from, one that does not depend on GitHub. The database gains one table, so read Upgrading before you pull.
New
-
Being told a new version is out. 2.1.1 and 2.1.2 were security releases, and nothing told the person running a server that they existed. Now the first administrator who signs in after the upgrade is asked whether the server may check
download.waveflow.appfor new versions: once shortly after each start, then once a day, and at once when the check is turned on. Nothing is sent before the answer is yes, so a server left at its defaults still contacts nobody on its own.Once the check is on, a dialog names the new version when one is out, links its release page, and says how to update a container or an archive. It can be dismissed for that version: the next release is announced again. Settings › Administration holds the switch and says when the server last checked. Only administrators see any of it.
The request fetches a small file,
server/stable.json, orserver/beta.jsonon a pre-release. It carries the server's version in its User-Agent and nothing else about the server: no identifier, no account, no count of anything. The host sees the address the request comes from, as any host contacted does. What leaves the server says exactly what is sent and when.Two settings decide for an operator who would rather not be asked.
WAVEFLOW_UPDATE_CHECKisaskby default;onchecks without asking, andoffnever checks and asks nobody.WAVEFLOW_UPDATE_URLnames another folder, for a fork that publishes its own. The decisions are in RFC-028, and the change is #435.This is the first release that can tell you about the next one. A server older than 2.2.0 has no check, so this one you still have to hear about the old way.
-
A second place to download from. Each release's archives, their
SHA256SUMS, the FFmpeg sources and the release notes are now also published ondownload.waveflow.app. The folder lists nothing, so start from the release's notes there,https://download.waveflow.app/server/releases/<tag>/RELEASE-NOTES.md, whose download links point at the copies. Before the copy counts, every archive and the FFmpeg sources are read back from that address and checked againstSHA256SUMS, and the notes are read back and compared byte for byte. The archives of 2.1.0-rc.1 to 2.1.2 are already there. The code and every signed tag are also kept on a backup outside GitHub. The change is #434.
For client authors
The native API gains GET and PUT /api/v2/server/update, for administrators only, answered as the other administrative routes are: 401 without a credential, 403 for an account or a token without administrative authority. Nothing else changes, and the Subsonic façade answers as it did.
Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.2.0The image runs on linux/amd64 and linux/arm64, and is the same, digest for digest, on Docker Hub as instazdll/waveflow-server:2.2.0. latest now follows this release. The standalone archives for Windows, macOS and Linux are attached below, with SHA256SUMS over all of them. The same files are linked from this release's notes on download.waveflow.app, which hold whether or not GitHub answers.
Upgrading from 2.1.2
Pull 2.2.0 and start it on the same data volume. With an archive, use the new folder in place of the old one, carrying over what you changed in laptop.env: your data is kept apart from that folder.
- The database schema gains one table, which holds the administrator's answer about the update check. Going back therefore takes a restore:
2.1.2refuses to start on a database2.2.0has migrated. The boot first copies the database and its key intopre-migration-<epoch ms>/in the data directory, andwaveflow-server database restoreputs that copy back, without anything the database gained since. WithWAVEFLOW_MIGRATION_BACKUPS=0the boot takes no copy, and the way back is a backup of your own. - The first administrator to sign in is asked about the update check. Answer as you like; the choice can be changed in Settings › Administration. To decide for everyone in advance, set
WAVEFLOW_UPDATE_CHECK=onoroff, and nobody is asked.
Coming from 2.1.1 or earlier, read the 2.1.2 notes first: they are a security fix. Coming from 2.0.1 or earlier, also read the 2.1.0 notes: that upgrade measures every lossless file again.
Full changelog: CHANGELOG.md · v2.1.2...v2.2.0
Download
The server for one computer, without Docker: extract the archive and start waveflow-server inside it. Each archive holds the server, its own FFmpeg and a README.txt to read first.
| System | Archive |
|---|---|
| Windows (x86_64) | waveflow-server-2.2.0-windows-x86_64.zip |
| macOS (Apple silicon) | waveflow-server-2.2.0-macos-arm64.zip |
| macOS (Intel) | waveflow-server-2.2.0-macos-x86_64.zip |
| Linux (x86_64) | waveflow-server-2.2.0-linux-x86_64.tar.gz |
| Linux (arm64) | waveflow-server-2.2.0-linux-arm64.tar.gz |
The Windows executables carry a self-signed certificate and the macOS ones an ad-hoc signature, which neither system trusts, so both warn on the first launch: README.txt says what to click past. SHA256SUMS holds the checksums of them all, and ffmpeg-8.1.3-sources.tar.gz the source of the FFmpeg they carry.
Container image
docker pull ghcr.io/instazdll/waveflow-server:2.2.0The same image, digest for digest, is on Docker Hub:
docker pull instazdll/waveflow-server:2.2.0Also published as latest in both, for linux/amd64 and linux/arm64 (a Raspberry Pi 4 or 5 on a 64-bit system).
v2.1.2
WaveFlow Server 2.1.2
A security release that completes 2.1.1. The check 2.1.1 added against a crafted audio file could be walked around by a WAV built for the purpose, and it refused some large files that were perfectly sound. Both are fixed here, and nothing else changes, so upgrade as soon as you can.
Security advisory: GHSA-6qhc-763f-95v3.
Security
-
A crafted WAV can no longer carry nested chapter frames past the check. 2.1.1 refuses a tag whose ID3v2 chapter frames nest, before the tag reader meets it, because that reader recurses without limit and a nested tag of a few kilobytes aborts the whole process. In a WAV, the check found the tag by walking the file's chunks the way it expected the reader to walk them. The reader does not always walk them that way.
A WAV can hold a
LIST INFOchunk, a list of text items such as a title or an artist. The reader goes through those items one by one, and when it stops partway — at an item whose name is not a valid one, or whose length runs past the list — it carries on with the rest of the file from where it stopped, not from the end of the list. A chunk header placed at that exact spot could then send the reader anywhere in the file, onto a tag the check never looked at, since the check had gone on from the end of the list.This was measured on 2.1.1 itself, with nine files built for the purpose. The server's own call to the reader read the tag in all nine, and the check let the crafted twin through in six. What those measurements show is the tag reaching the parser that 2.1.1 protects, through the same call every tag in a WAV goes through. No process was crashed along this path: the crash is the one the 2.1.1 notes describe.
The file arrives the same two ways as before: sent by an account allowed to upload to a library, or placed in a scanned folder. 2.1.1 is affected. Releases before it are affected by the original flaw, which they do not check for at all.
The check now works out every place the reader carries on from after such a list, and looks there too. Each of those places is held by a test that reads the same file through the reader itself, so a change in the reader fails the tests. A sweep of 200,000 malformed files, built from fixed seeds, runs with them. Each file is built twice around the same tag: the reader is given the copy whose tag it rejects with an error of its own, which shows it tried to parse that tag, and the part of the check that walks a WAV's or an AIFF's chunks is given the copy with nested chapter frames. No file whose tag the reader tries to parse may pass that walk. The fix is #431.
-
A file that cannot be read through is refused. When a read failed partway through a file, 2.1.1 took the failure for the end of a clean file and let it through, while the tag reader could read further and still meet nested chapter frames. Such a file is now refused: a scan records an error for it, and an upload is answered
422. A read that is merely interrupted is tried again, so a healthy file is not refused for it. This holds in a DSF or DFF file too, including for the few header bytes the check reads to find where such a file keeps its tag. A file that simply ends early is not a failed read, and passes as before: the reader meets the same end. The fixes are #429, #430 and #433.
Fixed
-
Large files that nest nothing are no longer refused. This is the known limit 2.1.1 shipped with. Its check gave up on a file, and refused it, once it had read 64 MiB of it, and two sound shapes of file made it read that much:
- a WAV or an AIFF that carries a chunk of odd length ahead of its audio;
- a FLAC that keeps the chunks of the WAV it was made from (
flac --keep-foreign-metadata), or any other file with the lettersRIFForFORMin its first 64 KiB.
The check no longer reads the audio in either. It walks chunks only in a file that opens as a WAV or an AIFF, and in those it reads the tags and nothing else, as the reader does. Measured on built files of 100 MiB to 3.5 GiB, 1,600 sizes for each way a writer pads an odd chunk: none refused.
After the upgrade, a file 2.1.1 refused is read again by the next scan, as a refused file is by every scan, and is then indexed like any other. The fixes are #430 and #431, for #428.
-
A refusal says what it is. 2.1.1 gave one reason for every file it refused, nested chapter frames, even when it had found none. The reason now tells three cases apart: the tag nests chapter frames, the file's metadata runs past what the check will read, or the file could not be read through its tags.
-
A full rescan keeps the track of a refused file. When a full rescan meets a file the check refuses, the track is marked unavailable rather than deleted. The file is untouched, and a later scan that reads it brings the track back.
Known limit: a file that changes while it is read
The check reads a file, and the tag reader then opens it again. A file that changes between the two, such as one still being copied into a scanned folder, is read by the reader as it then is, not as the check saw it. Where the check met the end of the file ahead of a tag, it found nothing to refuse, and the reader may find a tag there a moment later.
This has been so since 2.1.1. The check and the tag reader each open the file and read it at their own moment, so a change between those two reads cannot be ruled out. It takes write access to the library folder and a change that lands between the two reads. Uploading a file does not open that window by itself: the server receives the whole file, verifies its hash and moves it into place before reading it, so it never reads a file that is still arriving. That does not protect the file once it is in the folder. Someone who can write there can change an uploaded file like any other. Once the file is whole, every later read of it goes through the check again, the scan at the next start included, so such a file does not keep the server from starting. This was read from the code, not measured.
Not verified
- No process was crashed through the new path. The measurements stop at the tag reaching the parser.
- Every file measured was built for the purpose. None came from a real library.
- No read failure was provoked on a real disk. The tests fail a read through a reader built for that.
- The check follows the tag reader as it is today, lofty 0.25.5. The tests read through that reader, so a later version that walks a file differently fails them rather than passing unnoticed.
- The sweep of malformed files covers the shapes its generator builds, and that no file passed is evidence over those files, not a proof.
- Upgrading a library that holds a file 2.1.1 refused was not run end to end.
If you cannot upgrade yet
- Turn uploads off on every library, or leave upload rights only with people you trust.
- Keep the folders your libraries scan writable only by people and programs you trust. A file placed there directly is read by the next scan, whatever the upload setting.
- If your server already crashes at every start, remove the file from the library folder. It is usually the audio file uploaded or added last.
Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.1.2The image runs on linux/amd64 and linux/arm64, and is the same, digest for digest, on Docker Hub as instazdll/waveflow-server:2.1.2. latest now follows this release. The standalone archives for Windows, macOS and Linux are attached below, with SHA256SUMS over all of them.
Upgrading from 2.1.1
Pull 2.1.2 and start it on the same data volume. With an archive, use the new folder in place of the old one, carrying over what you changed in laptop.env: your data is kept apart from that folder. The database schema has not changed since 2.1.0, so there is nothing to migrate, and 2.1.1 still opens the same data should you need to go back.
Coming from 2.0.1 or earlier, read the 2.1.0 notes first: that upgrade changes the schema and measures every lossless file again.
Full changelog: CHANGELOG.md · v2.1.1...v2.1.2
Download
The server for one computer, without Docker: extract the archive and start waveflow-server inside it. Each archive holds the server, its own FFmpeg and a README.txt to read first.
| System | Archive |
|---|---|
| Windows (x86_64) | waveflow-server-2.1.2-windows-x86_64.zip |
| macOS (Apple silicon) | waveflow-server-2.1.2-macos-arm64.zip |
| macOS (Intel) | waveflow-server-2.1.2-macos-x86_64.zip |
| Linux (x86_64) | waveflow-server-2.1.2-linux-x86_64.tar.gz |
| Linux (arm64) | [waveflow-server-2.1.2-lin... |
v2.1.1
WaveFlow Server 2.1.1
Superseded by 2.1.2. The check this release added can be walked around by a WAV built for the purpose, and it refuses some large files that are sound. Upgrade to 2.1.2, which closes both. Security advisory: GHSA-6qhc-763f-95v3.
A security release. One audio file with a crafted tag could crash the server, and keep crashing it on every restart. Every release from 2.0.0 to 2.1.0 is affected, and so are the standalone archives that 2.1.0 introduced. Nothing else changes, so upgrade as soon as you can.
Security
-
A file whose ID3v2 chapter frames nest no longer crashes the server. A chapter frame (
CHAPorCTOC) can hold frames of its own, and the tag readers recursed once per nested level, without limit. A tag of a few kilobytes was enough to overflow the stack, which aborts the whole process and cannot be caught from inside it.The file could arrive in two ways. An account allowed to upload to a library could send it: the server put it in the library, then crashed reading it. Or it could be placed in a scanned folder, where the next scan read it. Either way the file stayed in the library, so the scan at start-up met it again and the server crashed on every restart, until someone found the file and removed it by hand.
The server now measures how deep a tag's chapter frames nest before reading it, in every format, and in the ID3 tag a DSF or DFF file carries. One chapter frame inside another is not refused: the limit is three levels under the first, well short of the depth that overflows the stack, and nothing real nests them at all. Past that limit:
- an upload of the file is refused with
422, and the file is taken back out of the library. Should the disk allow neither moving it back nor deleting it, the answer is503instead and the file stays, where a scan now refuses it rather than crashing on it; - a scan records an error for the file and goes on with the rest;
- the server stays up, and starts normally.
A real chapter layout, with a table of contents, titled chapters, links and pictures, is read as before. The check refuses rather than risks a crash, so a file that repeats the names
CHAPorCTOCseveral times in a place a reader does not always read to its end can be refused too.What was demonstrated is a loss of availability: the process aborts on a stack overflow. No effect on what the server discloses or stores was observed. The fix is #426.
- an upload of the file is refused with
Known limit: a large file refused by mistake
The check gives up on a file, and refuses it, once it has read 64 MiB of it. Two shapes of file make it read that much although they hold no chapter frame at all:
- a WAV or an AIFF that carries a chunk of odd length ahead of its audio;
- a file that is neither, with the letters
RIFForFORMin its first 64 KiB, such as a FLAC that keeps the chunks of the WAV it was made from (flac --keep-foreign-metadata).
This was measured on files built for the purpose, not on files found in a library: at 70 MiB each of the three was refused, and at 10 MiB each passed.
For such a file the scan counts an error and the file does not appear in the library, or its upload is answered 422. The reason given speaks of nested chapter frames, though there are none. The file itself is never changed. A refused file is read again, and refused again, by every scan. A file that was already in the library before the upgrade is not: an ordinary scan does not open a track's file while it has not changed, so the track stays as it is. #428 tracks the fix.
If you cannot upgrade yet
- Turn uploads off on every library, or leave upload rights only with people you trust.
- If your server already crashes at every start, remove the file from the library folder. It is usually the audio file uploaded or added last.
Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.1.1The image runs on linux/amd64 and linux/arm64, and is the same, digest for digest, on Docker Hub as instazdll/waveflow-server:2.1.1. latest now follows this release. The standalone archives for Windows, macOS and Linux are attached below, with SHA256SUMS over all of them.
Upgrading from 2.1.0
Pull 2.1.1 and start it on the same data volume. With an archive, use the new folder in place of the old one, carrying over what you changed in laptop.env: your data is kept apart from that folder. The database schema has not changed, so there is nothing to migrate, and 2.1.0 still opens the same data should you need to go back.
A server that crashed on such a file needs nothing more than the upgrade: 2.1.1 starts, its scan records an error for the file, and the file stays where it is until you remove it.
Coming from 2.0.1 or earlier, read the 2.1.0 notes first: that upgrade changes the schema and measures every lossless file again.
Full changelog: CHANGELOG.md · v2.1.0...v2.1.1
Download
The server for one computer, without Docker: extract the archive and start waveflow-server inside it. Each archive holds the server, its own FFmpeg and a README.txt to read first.
| System | Archive |
|---|---|
| Windows (x86_64) | waveflow-server-2.1.1-windows-x86_64.zip |
| macOS (Apple silicon) | waveflow-server-2.1.1-macos-arm64.zip |
| macOS (Intel) | waveflow-server-2.1.1-macos-x86_64.zip |
| Linux (x86_64) | waveflow-server-2.1.1-linux-x86_64.tar.gz |
| Linux (arm64) | waveflow-server-2.1.1-linux-arm64.tar.gz |
The Windows executables carry a self-signed certificate and the macOS ones an ad-hoc signature, which neither system trusts, so both warn on the first launch: README.txt says what to click past. SHA256SUMS holds the checksums of them all, and ffmpeg-8.1.3-sources.tar.gz the source of the FFmpeg they carry.
Container image
docker pull ghcr.io/instazdll/waveflow-server:2.1.1The same image, digest for digest, is on Docker Hub:
docker pull instazdll/waveflow-server:2.1.1Also published as latest in both, for linux/amd64 and linux/arm64 (a Raspberry Pi 4 or 5 on a 64-bit system).
v2.1.0
WaveFlow Server 2.1.0
The server now runs on one computer without Docker: extract an archive and start it. An account can say once what an app that does not ask should get. The web client comes in seven more languages. The image moves to Debian 13, and its first boot after upgrading measures every lossless file again, so read Upgrading before you pull.
New
-
An archive for each system, with nothing to install. Each release now carries one for Windows, for macOS (Apple silicon and Intel) and for Linux (x86_64 and arm64). It holds the server, its own FFmpeg, a
laptop.envof settings, the licences and aREADME.txtto read first. On a first start the browser opens by itself on the sign-in screen; on Windows and macOS, unless the server listens on a wildcard address, the setup token is already filled in.SHA256SUMScovers every archive.The Windows executables carry a self-signed certificate and the macOS ones an ad-hoc signature. A file changed after signing no longer matches its signature, but neither system trusts these signatures, so both still warn on the first launch. The
README.txtsays what to click past. -
Transcoding profiles. In Settings › Playback, or through
/api/v2/transcoding-profiles, an account sets a format and a bit rate for apps that do not ask, as a default and per Subsonic client name. A request naming a format or a bit rate is answered as before, and so is every request of an account with no profile. The web player, downloads and public shares keep the original.Two limits come with it. A transcode past the server's limits is refused with
429rather than falling back to the original: 2 per account and 4 in all by default, set byWAVEFLOW_TRANSCODE_PER_USER_LIMITandWAVEFLOW_TRANSCODE_GLOBAL_LIMIT. And an app that seeks by byte range may fail to seek in a transcode still being encoded; a row of Original for that app puts it right. -
Seven more languages in the web client: German, Spanish, Italian, Japanese, Korean, and Simplified and Traditional Chinese, beside English and French. Settings › Language offers each, and a browser set to one opens in it. Native speakers reviewed all seven.
-
An open tab follows the library. What a scan, an upload from another device or a tag correction changed appears without a reload.
-
The transcoding state in the administration: whether the server can transcode, the transcodes running against the limits, and the output formats.
-
waveflow-server healthasks/readyatWAVEFLOW_BINDand exits 0 on its readiness answer, or prints one line and exits 1.
Changed
- The image runs on Debian 13 (trixie) instead of Debian 12, and its FFmpeg goes from 5.1 to 7.1.
- curl has left the image. Its
HEALTHCHECKnow runswaveflow-server health.
For client authors
The native API only gains: /api/v2/transcoding-profiles, and events_cursor on each library in GET /api/v2/libraries, the point a client that has just read the catalogue starts following the change feed from. The Subsonic façade answers as it did for an account with no profile. Under a profile, a stream or a native stream that names neither a format nor a bit rate may now be transcoded, a source at or under the bit rate excepted.
Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.1.0The image runs on linux/amd64 and linux/arm64, and is the same, digest for digest, on Docker Hub as instazdll/waveflow-server:2.1.0. latest now follows this release.
Upgrading from 2.0.1
Pull 2.1.0 and start it on the same data volume. Three things differ from the last upgrade.
- The first boot of the image measures every lossless file again. A quality measurement is current only for the FFmpeg that took it, so the boot deletes them all and the background analysis takes each file anew, one at a time by default and never during a scan. On a large library that is hours of one core, and each file reads
pendinguntil its turn comes.WAVEFLOW_QUALITY_PARALLELISMpaces it: raise it to finish sooner, or set it to0to hold the analysis off until a quieter moment. - The database schema changes, so going back requires a restore.
2.0.1refuses to start on a database2.1.0has migrated. The boot first copies the database and its key intopre-migration-<epoch ms>/in the data directory, andwaveflow-server database restoreputs that copy back, without anything the database gained since, and leaves the rest of the data directory as it is. WithWAVEFLOW_MIGRATION_BACKUPS=0the boot takes no copy, and the way back is a backup of your own. - A healthcheck of your own that calls curl now fails, because curl is no longer in the image. Remove it to use the image's own, or call
waveflow-server health.
Full changelog: CHANGELOG.md · v2.0.1...v2.1.0
Download
The server for one computer, without Docker: extract the archive and start waveflow-server inside it. Each archive holds the server, its own FFmpeg and a README.txt to read first.
| System | Archive |
|---|---|
| Windows (x86_64) | waveflow-server-2.1.0-windows-x86_64.zip |
| macOS (Apple silicon) | waveflow-server-2.1.0-macos-arm64.zip |
| macOS (Intel) | waveflow-server-2.1.0-macos-x86_64.zip |
| Linux (x86_64) | waveflow-server-2.1.0-linux-x86_64.tar.gz |
| Linux (arm64) | waveflow-server-2.1.0-linux-arm64.tar.gz |
The Windows executables carry a self-signed certificate and the macOS ones an ad-hoc signature, which neither system trusts, so both warn on the first launch: README.txt says what to click past. SHA256SUMS holds the checksums of them all, and ffmpeg-8.1.3-sources.tar.gz the source of the FFmpeg they carry.
Container image
docker pull ghcr.io/instazdll/waveflow-server:2.1.0The same image, digest for digest, is on Docker Hub:
docker pull instazdll/waveflow-server:2.1.0Also published as latest in both, for linux/amd64 and linux/arm64 (a Raspberry Pi 4 or 5 on a 64-bit system).
v2.1.0-rc.1
Download
The server for one computer, without Docker: extract the archive and start waveflow-server inside it. Each archive holds the server, its own FFmpeg and a README.txt to read first.
| System | Archive |
|---|---|
| Windows (x86_64) | waveflow-server-2.1.0-rc.1-windows-x86_64.zip |
| macOS (Apple silicon) | waveflow-server-2.1.0-rc.1-macos-arm64.zip |
| macOS (Intel) | waveflow-server-2.1.0-rc.1-macos-x86_64.zip |
| Linux (x86_64) | waveflow-server-2.1.0-rc.1-linux-x86_64.tar.gz |
| Linux (arm64) | waveflow-server-2.1.0-rc.1-linux-arm64.tar.gz |
The Windows executables carry a self-signed certificate and the macOS ones an ad-hoc signature, which neither system trusts, so both warn on the first launch: README.txt says what to click past. SHA256SUMS holds the checksums of them all, and ffmpeg-8.1.3-sources.tar.gz the source of the FFmpeg they carry.
Container image
docker pull ghcr.io/instazdll/waveflow-server:2.1.0-rc.1The same image, digest for digest, is on Docker Hub:
docker pull instazdll/waveflow-server:2.1.0-rc.1Also published as beta in both, for linux/amd64 and linux/arm64 (a Raspberry Pi 4 or 5 on a 64-bit system).
What's Changed
- docs: say how to mount the music folder under selinux, and name the readme's table columns by @InstaZDLL in #390
- feat(webapp): add simplified chinese by @InstaZDLL in #391
- feat(webapp): add german, spanish, italian, japanese, korean and traditional chinese by @InstaZDLL in #392
- build(docker): move the image from bookworm to trixie by @InstaZDLL in #411
- feat(cli): add a health subcommand and drop curl from the image by @InstaZDLL in #412
- ci: build and boot the container image on both architectures by @InstaZDLL in #413
- feat(standalone): open the browser on start, with the setup token in the fragment by @InstaZDLL in #410
- build(ffmpeg): a minimal lgpl ffmpeg, built in ci for five targets by @InstaZDLL in #407
- feat(webapp): show the transcoding state in the administration by @InstaZDLL in #408
- feat(webapp): follow the library event stream by @InstaZDLL in #409
- ci(release): attach the standalone archives and SHA256SUMS by @InstaZDLL in #415
- fix(standalone): hand the setup token over only where nothing else can take it by @InstaZDLL in #414
- ci(release): sign the standalone executables by @InstaZDLL in #417
- docs(rfc): decide against jukebox mode by @InstaZDLL in #416
- feat(streaming): per-account transcoding profiles by @InstaZDLL in #418
- chore(release): prepare 2.1.0-rc.1 by @InstaZDLL in #419
- chore: bump the cargo-patch-and-minor group with 6 updates by @dependabot[bot] in #420
- chore: bump the npm-patch-and-minor group in /webapp with 4 updates by @dependabot[bot] in #421
Full Changelog: v2.0.1...v2.1.0-rc.1
v2.0.1
WaveFlow Server 2.0.1
A small release after 2.0.0: a ready-made way to put the server behind HTTPS, and one needless boot warning removed.
New
-
HTTPS with Caddy, ready to copy.
packaging/caddy/holds a compose file and a Caddyfile that run the server behind Caddy. Set the name your clients will use in a.envand rundocker compose up -d. A public name gets a certificate from Let's Encrypt, or from ZeroSSL when Let's Encrypt cannot issue one. A name ending in.internalor.home.arpa, or an IP address, gets one from Caddy's own local authority. The server publishes no port of its own, and the proxy is already trusted, so sign-in protection counts each client by its own address.The operations guide explains how to make your devices trust the local authority, why an untrusted certificate stops offline listening in the browser, why most Android apps refuse the local authority even once you install it, how to move a server you already run onto this setup without losing its data, and what to write for nginx instead.
Fixed
- A server that never asked for Last.fm no longer warns about it at every boot. The warning now appears only when you named a Last.fm instance yourself, with
WAVEFLOW_SCROBBLE_LASTFM_URL.
Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.0.1The image runs on linux/amd64 and linux/arm64, and is the same, digest for digest, on Docker Hub as instazdll/waveflow-server:2.0.1. latest now follows this release.
Upgrading from 2.0.0
Pull 2.0.1 and start it on the same data volume. The database schema has not changed, so there is nothing to migrate, and 2.0.0 still opens the same volume should you need to go back.
To move onto the Caddy setup, stop the server you run now before starting the new one, and point the new compose file at your existing volume with WAVEFLOW_DATA_VOLUME: the guide's "Moving an existing server" walks through it. Started without that line, it begins on a new, empty volume.
Full changelog: CHANGELOG.md · v2.0.0...v2.0.1
Container image
docker pull ghcr.io/instazdll/waveflow-server:2.0.1The same image, digest for digest, is on Docker Hub:
docker pull instazdll/waveflow-server:2.0.1Also published as latest in both, for linux/amd64 and linux/arm64 (a Raspberry Pi 4 or 5 on a 64-bit system).
v2.0.0
WaveFlow Server 2.0.0
The first stable release. WaveFlow Server streams the music you own to your browser, your phone and your desktop, from one Rust binary and SQLite, and answers the Subsonic apps you already use. Since the last beta it has grown a library browser, artist pages, smart playlists, podcasts, internet radio, statistics and mixes, and it measures the real quality of your lossless files. Four Subsonic apps were run against it before this release, and every server defect they found is fixed.
New since 2.0.0-beta.1
- Artist pages: biographies and similar artists from Last.fm, portraits from Deezer and fanart banners from TheAudioDB once you switch them on, or a portrait and a banner you choose yourself.
- The real quality of your files: every lossless file is measured from its audio in the background, and a FLAC that was once an MP3 is shown for what it is.
- Your library, your way: browse by folders as well as by tags, split a joined credit such as
Artist A, Artist Binto two artists, and see on one page what needs a decision. - Smart playlists that fill themselves from rules, podcasts, and internet radio, all playable from the play bar.
- Discovery: listening statistics, mixes made from your own listening, similar songs drawn from your library, and lyrics from LRCLIB, when you switch it on, for a file that has none.
- Playlists and sharing: a track can appear twice in a playlist, public playlists for the other accounts,
.m3uimport and export, and share links that open a player page in any browser. - Scanning that keeps up: only what changed is read again, one damaged folder no longer stops the rest, a watcher picks up new files, and a missing file keeps its favourites, playlist entries and play history until it returns. A purge can be undone for a week.
- Playback: AAC alongside MP3 and Opus, server-side volume levelling for Subsonic apps, and ReplayGain in the web player.
- The web player: a Songs page with search as you type, synced lyrics with an immersive view, technical details and a quality meter, a Settings page, and an artist's most played tracks.
- Safer by default: sign-in is protected against guessing, a one-time token guards the first-run setup, and a lost or compromised instance key can be replaced.
- Outside Docker: the server finds FFmpeg beside its own executable, and laptop mode (
WAVEFLOW_LAPTOP=true) keeps its data in your user folder, listens only on this computer, and never starts twice. It prepares the double-click downloads planned for a later release. - The source is one click away: the sign-in page and Settings link to the source of the exact version running, as its AGPL licence asks.
Fixed
- DSub: its shares screen no longer crashes, an album can be shared, starred composers no longer appear as starred artists, and a track with no album can be reached by folders.
- A removed track is refused by every read and by every write that targets it, while its favourites and ratings stay attached and come back with the file. Saving a play queue is the exception: the queue is saved without the tracks that went.
- An album whose files have all gone no longer stays listed, empty.
- iPhone players seeking in a converted stream get a proper answer.
- Search no longer finds an album by the title of a track that was removed.
- A genre written twice in one file no longer stops a whole scan batch.
Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.0.0The image runs on linux/amd64 and linux/arm64, which includes a Raspberry Pi 4 or 5 with a 64-bit system. It is the same image, digest for digest, on Docker Hub as instazdll/waveflow-server:2.0.0, and latest now follows this release.
Open http://your-host:4533: the setup screen asks for the one-time token the server wrote to /data/setup-token. The README has the next steps.
Upgrading from a beta
Pull 2.0.0 and start it on the same data volume. The server copies the database and its key aside before it migrates, so a failed upgrade can be rolled back from that copy. Keep waveflow.db and instance.key together in your backups: the Subsonic passwords and scrobbling links in the database are sealed with that key, share links are derived from it, and the server refuses a mismatched pair at startup.
Two changes a client may notice:
- DSub is answered in plain Subsonic. It receives no OpenSubsonic fields, and its XML comes as
text/xml.WAVEFLOW_SUBSONIC_LEGACY_CLIENTSlists the apps treated this way; set it empty to answer every app in full. getPlayQueueanswers no element at all before a queue has been saved, as the Subsonic reference does.
Tested with
Symfonium 15.1.0 and DSub 5.5.3 on Android, Feishin 1.17.0 on Windows, and Juliet 8 on an iPhone, on 2026-10-27 and 2026-10-28. What each app does and does not use is in the compatibility matrix.
Known limitations
- Juliet sends the account password as an API key once it has read the server's extensions, and crashes when that is refused. A seek made after that point fails; everything before it works.
- No 32-bit ARM image: a Raspberry Pi running a 32-bit system needs a 64-bit one.
- The image needs FFmpeg, which it carries; a binary run outside Docker needs FFmpeg and ffprobe on
PATHor beside it.
Full changelog: CHANGELOG.md · v2.0.0-beta.1...v2.0.0
Container image
docker pull ghcr.io/instazdll/waveflow-server:2.0.0The same image, digest for digest, is on Docker Hub:
docker pull instazdll/waveflow-server:2.0.0Also published as latest in both, for linux/amd64 and linux/arm64 (a Raspberry Pi 4 or 5 on a 64-bit system).
v2.0.0-beta.1
2.0.0-beta.1 (2026-09-16)
✨ What's new in v2.0.0-beta.1
WaveFlow Server 2.0.0-beta.1 — your listens go somewhere, your library accepts files, and every client was replayed against the real thing. 361 commits since beta.0. Scrobbling to ListenBrainz, Last.fm and Maloja arrives with a queue that survives a restart; a library can now receive uploads; a track can carry a looping visual; and tags can be corrected without ever rewriting your audio files. Still one binary, one database file, one key file.
🎧 Your listens leave the server
Connect ListenBrainz, Last.fm or Maloja — each one a named instance, so you can point at your own Maloja and the public one at the same time.
What matters here is what happens when the other end is down. A listen leaves by a durable queue that survives a restart, is claimed before it is sent, retries once, and honours a standard Retry-After. The health it reports cannot read healthy while three thousand listens are waiting since this morning — that silent failure is the whole reason the queue is visible at all.
Last.fm authorises through your browser. A machine with no browser on it has its own journey, from the command line, and needs no public address.
🖼️ A track can carry a moving cover
Tracks can hold a canvas — the short looping visual you know from other players. It has its own store, its own routes and its own tickets, and a sweep that collects what a failed upload left behind.
⬆️ Send files to your library
A library can be told to accept files. The server decides whether it wants a given file, receives it in chunks under a bounded session, and makes it a track. Uploads land through the web client or the API.
✏️ Fix a tag without touching the file
Correct a track's tags, artists and genres from the client. Your audio files are never rewritten — corrections live beside them. A correction leaves alone what it does not mention, and an empty one writes nothing and says so.
📡 Know what changed, without rescanning
/api/v2 gained a library change feed, with a watermark each device acknowledges for itself, a retention trim, and an honest answer when the stream has been cut rather than a silent gap.
🎵 More of what Subsonic clients ask for
Albums now answer originalReleaseDate, releaseDate, releaseTypes[], recordLabels[] and discTitles[] wherever your tags carry them. Search can be scoped to a single library, on both surfaces.
🌐 The web player grew into the screens it was missing
Uploads, canvas placement, a track-tag editor, a live view of a running scan, API tokens, library membership, sort and filter on every browse page, player modes, and the four gestures external scrobbling leaves to a client.
🔌 For client authors — three response shapes changed
None of these break a compile. All three change what arrives on the wire.
1. getPlayQueue and getNowPlaying name their children entry, not song — and playQueue now carries the username the schema requires. The Subsonic schema renames a media item inside those two containers exactly as it does inside a playlist; this server sent song in both until now, and a client that decodes against the schema read an empty queue.
Both names are deliberately not emitted together: a response carrying
entryandsongconforms to neither contract, and would need a second wire change later to undo. A beta is the moment to do this properly — the contract freeze protects clients that were validated, not a divergence from the specification.
2. GET /api/v2/scrobble-destinations — the unavailable field carries a case (no_application_configured, browser_journey_needs_https) instead of an English sentence. That sentence was printed verbatim into a client that ships in two languages, which told a French reader, in English, what to change in their configuration.
3. GET /api/v2/scans/{id}/events — the progress frame now speaks the same shape as GET /api/v2/scans/{id}: id, total_files, processed_files instead of scan_id, total, processed. A watcher bound to total_files used to show undefined from the first frame on.
🐛 Fixed
Four defects found by the client campaigns, all present in beta.0 as well — none is a regression this release introduces.
- A composer credit no longer becomes several artists. A tag reading
Kobee (Melange / INHOUSE), Holy M (Melange / INHOUSE)names two people; the slashes inside the parentheses were cutting it into three entities, each carrying a parenthesis it never opened, and they reached the catalogue as artists that search answered with.Bach/Gounodis still two people andAC/DCis still one band. - A
416no longer claims a resource is empty. A transcode still being produced has no complete length, andbytes */0did not say unknown — it said empty, which a client may believe and never ask about again. - A refusal that outlived its session neither renews nor replays. A sign-out and a sign-in fit inside one round trip, so a
401raised for one account could arrive after another had signed in, spending the new account's refresh token for a request the old one made. - Plus fixes across the scrobbling queue, the canvas store, upload sessions, the event feed's tenancy and the web client's session handling.
Also from earlier in this cycle: the transcode a seek abandons is now cached, artist favourites survive a change of identity spec, and the track relocation hint is dropped wholesale when its spec changes rather than compared across two.
✅ What was actually verified
Nothing below was read from a client's display.
Four real clients, replayed on 2026-09-16 against the façade as it now stands — Symfonium 15.0.1 and DSub 5.5.3 on an Android 17 emulator, Juliet on a physical iPhone, Feishin 1.15.1 on Windows desktop. Playback and seeking on cold transcodes, catalogue browsing, the full playlist cycle, favourites, ratings, scrobbles and the server-side play queue — every result read back from server state. Seeking by byte range was re-established on iOS and on desktop. See the compatibility matrix.
The upgrade from 2.0.0-beta.0, which nothing in the repository would have caught, because every integration test starts from an empty database: the tagged binary built a database from a real scan of 164 files, and the current binary opened it. 16 migrations, all successful, schema 22 → 38, catalogue and full-text search intact.
📦 Install
docker run -d --name waveflow \
-p 4533:4533 \
-v waveflow-data:/data \
-v /path/to/music:/music:ro \
ghcr.io/instazdll/waveflow-server:2.0.0-beta.1Or with Compose — set WAVEFLOW_MUSIC_PATH to your music directory, mounted read-only:
WAVEFLOW_MUSIC_PATH=/path/to/music docker compose up -dThen create the admin account and register a library:
docker exec -e WAVEFLOW_ACCOUNT_PASSWORD='at-least-twelve-characters' \
waveflow waveflow-server account create-admin --username admin
docker exec waveflow waveflow-server library add --owner admin --name Music --path /musicPublished for linux/amd64 and linux/arm64, and also tagged beta.
Upgrading from 2.0.0-beta.0
Pull and restart. The 16 migrations run on first boot and were rehearsed on a real catalogue before this tag.
Back up data/waveflow.db and data/instance.key together — they are one unit, and encrypted values cannot be recovered with one without the other. If you have ever lost a key, the way back is now documented in the operations guide.
Known issue
#229 — getPlayQueue with nothing saved answers a bare playQueue, which the schema has no valid empty form for. Conforming means removing the element entirely, a further wire change on the one call every client makes at startup; it is pinned by a contract test and left to decide after the beta. No client has been observed to mind.
This is a beta. If something behaves differently on your client, open an issue — the last campaign found four defects that no automated test could reach. 💚
