# Aonsoku is a React and Electron client for Navidrome and Subsonic servers

> Aonsoku is a desktop, web and container client for Subsonic compatible music servers, written in React and Electron with an nginx based container image. Its interesting engineering is in the packaging rather than the player, and its environment variable based automatic login carries a warning the documentation states plainly.

**victoralvesf/aonsoku** — A modern desktop client for Navidrome/Subsonic servers built with React and Electron.

- Repository: https://github.com/victoralvesf/aonsoku
- Website: https://aonsoku.vercel.app
- Stars: 981 · Forks: 91
- Language: TypeScript
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/victoralvesf-aonsoku

## What Aonsoku is and which servers it talks to

Aonsoku is a client for self-hosted music servers, specifically Navidrome and Subsonic servers, built with React and Electron. The repository description calls it a modern desktop client, and the project publishes more than one shape of the same application: a desktop app, a web app hosted at aonsoku.vercel.app, and a container image.

That matters because the client is not tied to one server implementation. The application carries a SERVER_TYPE setting whose documented values are subsonic, navidrome and lms, and the description of that variable says it is used by the app to adjust some features and improve usability based on the server. So the UI can behave differently depending on what the server actually supports, which is a more honest approach than assuming every server implements the full protocol.

The audience is people who run their own music server. That is a small, self-sufficient group, and the feature list is built around what such a setup is missing: a client that is quick, that shows lyrics properly, and that reports plays back so the server can build listening statistics.

The application is a private package on npm named aonsoku, with a product name of Aonsoku and the author recorded as Victor Alves. The current version in the package manifest is 0.15.0.

## One codebase, three ways to deliver it

The repository contains a Vite web application and an Electron wrapper, and it also produces a container image, which is unusual for a music client and is the most interesting thing about the project.

The desktop app is built with electron-vite, and the package scripts show the whole delivery matrix: build:win, build:mac and build:linux produce installers through electron-builder, and there are parallel ci scripts that publish straight to a release channel. The main entry point for the packaged app is ./out/main/index.js.

The web app is a plain Vite build with the script build running tsc and then vite build, and the same output is what the container image serves. That is why there is one source tree and three deliveries rather than three codebases: the desktop app is the web app inside Electron with a preload and a main process around it.

The container route is documented as a Docker compose file and as a Podman Quadlet unit, and the project also ships a Flatpak directory and a dev-app-update.yml for in-app updates on desktop builds. The breadth of packaging targets is what tells you the maintainer cares about distribution more than about the feature set, which is the right priority for a client whose job is to be installed on other people's machines.

## Lyrics, radio and scrobbling each degrade differently

The feature list is short, and the interesting part is in how each feature handles the case where the server cannot help.

Lyrics come in two forms. Synchronised lyrics are fetched from LRCLIB when the server does not provide one, which means a track with no synced lyrics in your library can still get word-by-word timing from an external source. Unsynchronised lyrics are read from tags embedded in your own files, so the app can display plain lyrics when they exist on disk even if the server has nothing to serve.

Radio is conditional in the way you would expect: if the server supports it, you can listen to radio shows inside the app. That is a server capability, not something the client can synthesise.

Scrobbling is the opposite direction, sending play information back to your server so it can record what you listened to.

Podcasts are a special case worth calling out, because they are not part of this repository. The documentation describes podcast access, management and listening as coming from Aonsoku Podcasts, a separate project under the same author. So the desktop client is the shell and the podcast feature is an addition living elsewhere, which is a reasonable modular choice but also means the podcast experience is not covered by this repository's release history.

Radio is also exposed as a configurable section in the interface, with a HIDE_RADIOS_SECTION switch in the environment configuration, so a deployment that does not want radio exposed can turn it off before users see it.

## Running it from source: clone, install, pick a mode

The prerequisites are Node.js, a package manager of your choice with pnpm named first, and cargo. Cargo is on the list because the repository carries Rust tooling alongside the TypeScript sources.

Installation from source is two steps.

```sh
git clone https://github.com/victoralvesf/aonsoku.git
```

```sh
pnpm install
```

Then you choose how to run it. The web app starts with:

```sh
pnpm run dev
```

and the desktop app with:

```sh
pnpm run electron:dev
```

The desktop script is electron-vite dev --watch, which means the Electron main process, the preload and the renderer are all rebuilt as you edit, and the watch mode is what makes the desktop iteration loop usable.

There is a wrinkle in the install step that only bites in containers. The package defines a postinstall script running electron-builder install-app-deps, which rebuilds native dependencies against the local Electron version. The Dockerfile sets PNPM_CONFIG_IGNORE_SCRIPTS=true and runs pnpm install --ignore-scripts for exactly this reason, since the container image does not need Electron native modules rebuilt for a desktop runtime that will never be present.

## The container image is nginx serving a static build on port 8080

The Dockerfile is a two stage build. The first stage is node:24-alpine with corepack enabled, which copies the package manifest and lockfile, installs with scripts ignored, copies the source and runs the build. The second stage is nginx:alpine, and it copies the built dist directory into the nginx document root.

```dockerfile
FROM nginx:alpine

COPY --chown=nginx:nginx --from=build /app/dist /usr/share/nginx/html
```

That single fact explains the whole deployment story. The image contains a static web app and a web server, not a Node runtime, so the runtime footprint is small and the container does not need to execute the app server side.

Two template files travel with it. nginx.conf.template is copied into the nginx templates directory so the entrypoint can render it, and env-config.js.template is copied into the document root, which is how the browser build learns its runtime configuration. The set-variables.sh script is installed into the nginx entrypoint directory, which is the standard nginx image mechanism for generating configuration at startup.

The compose file exposes the app on port 8080 and passes the environment variables through.

```yaml
version: '3.8'

services:
  aonsoku:
    container_name: aonsoku
    image: ghcr.io/victoralvesf/aonsoku:latest
    restart: unless-stopped
    ports:
      - 8080:8080
```

The images are published to the GitHub container registry under the maintainer's namespace with the latest tag, and the Podman Quadlet unit offers the same image with AutoUpdate=registry for systemd based hosts.

## Automatic login through environment variables, and the warning attached to it

The environment configuration is the part to read twice before you deploy. The variables are simple and documented in the .env.example file.

```bash
PORT=8080
SERVER_URL=http://your-subsonic-server:port
APP_AUTH_TYPE=token
SERVER_TYPE=subsonic
```

The interesting ones are APP_USER and APP_PASSWORD, which together enable automatic login across every device that opens the instance, so a user does not have to type credentials. The documentation attaches two notes to that feature. The configuration should only be used in local environments, and if the environment is publicly exposed the password could be compromised.

That is a genuine limitation rather than a formality. The credential lives in a file that the web tier reads at startup, and if the instance is reachable from the internet, anyone who can reach the login form has a path to a shared account. The .env.example also warns separately that automatic login is convenient for a personal instance across devices, which is exactly the case where the exposure is smallest.

HIDE_SERVER controls whether the server URL field is hidden on the login screen, which pairs with the same scenario. APP_AUTH_TYPE selects token or password authentication, and the note on the password method says it is intended for legacy authentication servers.

The rest of the configuration is presentation: APP_THEME picks one of a long list of themes from light and dark through named colour schemes, APP_HIDE_THEMES hides the theme picker, HIDE_RADIOS_SECTION removes the radio page, and IMAGE_CACHE_ENABLED turns the artwork cache on or off.

## Tooling, tests and the parts the documentation leaves open

The development stack is visible in the repository root. Vite and electron-vite do the building, TypeScript is configured separately for the web and node sides, Biome handles linting and formatting, Tailwind and components.json suggest a Radix based component set, and husky is wired in through the prepare script to install git hooks. Cypress provides the test story, with the test script running cypress run --component, which is component level testing rather than end to end.

Two directories point at features that are only partly described. The flatpak directory sits alongside the electron-builder configuration, so there is a Linux packaging route beyond the AppImage style builds, and dev-app-update.yml suggests desktop builds can update themselves.

The README carries sections for a roadmap, contributing, translation and licence, and a specific troubleshooting section for macOS users who see an app that cannot be opened or a crash on launch, which is the usual quarantine and signing behaviour on Apple silicon builds.

The licence is MIT, which is permissive and covers the whole repository including the web app, the Electron wrapper and the container files. The release history shows steady work: v0.13.0 on 2026-02-26, v0.14.0 on 2026-04-02 and v0.15.0 on 2026-07-13, with the last push on 2026-10-10. A version below 1.0 still means the configuration keys, environment variables and defaults can change between releases, so pin the version in your compose file rather than tracking latest if you care about a stable deployment.

## Conclusion

Aonsoku fits a self-hosted music server owner who wants a fast desktop client with synced lyrics, scrobbling and radio on top of Navidrome, or a browser based alternative for devices where installing a desktop app is not possible. It does not fit someone whose server implements a different API, someone who cannot accept that automatic login puts a password in a configuration file, or a team that needs a signed, notarised macOS build without doing that work themselves. Before deploying the container, decide whether you need automatic login at all, because the environment file that enables it is the same file that would expose a credential if the instance is reachable from outside your network.

## FAQ

### What servers does Aonsoku work with?

Aonsoku is a client for Navidrome and Subsonic servers. The SERVER_TYPE environment variable accepts subsonic, navidrome and lms, and the app uses it to adjust features based on the server it is talking to.

### How do I run Aonsoku from source?

You need Node.js, pnpm or another package manager, and cargo. Clone the repository, run pnpm install, then use pnpm run dev for the web app or pnpm run electron:dev for the desktop app.

### Can Aonsoku be deployed with Docker?

Yes. The compose file runs the published image ghcr.io/victoralvesf/aonsoku:latest with port 8080 published, and a Podman Quadlet unit is provided as well. The image is an nginx server serving the built static app rather than a Node server.

### How does automatic login work and is it safe to enable?

Setting APP_USER and APP_PASSWORD enables automatic login for your user across all devices using that instance, with APP_AUTH_TYPE choosing token or password authentication. The documentation notes that this should only be used in local environments and that the password could be compromised if the environment is publicly exposed.

### Where do Aonsoku lyrics come from?

Synchronised lyrics are fetched from LRCLIB when the server does not provide one, and unsynchronised lyrics are taken from lyrics embedded in your own files. Radio is available only if the server supports it.

## Sources

- [License: MIT](https://github.com/victoralvesf/aonsoku/blob/main/LICENSE)
- [Project website](https://aonsoku.vercel.app)
- [README](https://github.com/victoralvesf/aonsoku/blob/main/README.md)
- [Releases](https://github.com/victoralvesf/aonsoku/releases)
- [victoralvesf/aonsoku on GitHub](https://github.com/victoralvesf/aonsoku)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/victoralvesf-aonsoku
