Project Lyricova is a free and open source suite of web apps for lyrics blogging and music management focused on Vocaloid* contents.
Project Lyricova is currently under the progress of a complete rewrite. The rewrite will consist of 2 parts: a jukebox + music library manager and a lyrics blog. The new tech stack is TypeScript + Node.js + MySQL + Express.js + React + Next.js.
For music manangement, this project relies on VocaDB for its database of Vocaloid songs, and allow manual addition of entries that does not fall under VocaDB’s scope.
For the previous version of Project Lyricova last released in 2015, see the v1
branch.
* The term “Vocaloid” here includes all voice synthesizers, as defined in VocaDB.net.
api: The backend API for bothjukeboxandlyricova.jukebox: Music library manager and public jukebox.lyricova: Lyrics blog (similar to version 1).components: Common front-end logic and components shared betweenjukeboxandlyricova.lyrics-kit: Fetch and parse lyrics from various sources.glyph-renderer: A Rust/wasm crate that renders lyrics in a canvas with glyphs and effects.
- Database
-
docker composenow ships amysqlservice (MySQL 9.7) with a persistentmysql_datavolume, so you no longer need to provision one by hand. It is published on127.0.0.1:3306only. -
Create a MySQL user and grant it access to the database (the compose service does this from
MYSQL_USER/MYSQL_PASSWORD, defaultlyricova/lyricova). -
DB_URImust resolve to the database from wherever it runs: use themysqlservice name from inside compose, and127.0.0.1:3306from the host. -
Initialize (or update) the schema with Drizzle. From a deployment that runs the published image, use the one-shot
migrateservice — it runs the very image you are deploying, so the migration files always match the running code, and it needs no checkout, Node install or dev toolchain on the host:docker compose --profile migrate run --rm migrate
From a source checkout (e.g. for local development), the equivalent is:
npm run db:migrate --workspace @lyricova/api
Point it at the published port when the database is the compose service:
DB_URI="mysql://lyricova:lyricova@127.0.0.1:3306/lyricova?ssl=false" \ npm run db:migrate --workspace @lyricova/api
-
- Music file storage
- Create a directory for storing music files (defaulted to
/var/lyricova/music).
- Create a directory for storing music files (defaulted to
- Environment variables
- Configure the environment variables in
.envfile. Refer to.env.samplefor examples. NEXT_PUBLIC_*are build-time, not runtime. Next.js inlines them into the client bundle when the app is compiled, so with the self-contained Docker image they must be passed as build args, not container environment.docker composeforwardsNEXT_PUBLIC_POSTHOG_KEY,NEXT_PUBLIC_POSTHOG_HOST,NEXT_PUBLIC_CLARITY_PROJECT_IDandNEXT_PUBLIC_TELEMETRY_ENABLEDfrom your shell or.envinto the build.- The image already fixes the values that are properties of the image rather
than of a deployment:
YTDLP_PATH,MUSIC_FILES_PATH,NODE_ENV,API_PORT/LYRICOVA_PORT/JUKEBOX_PORTandAPI_INTERNAL_URL(all three processes share one network namespace).FFMPEG_PATHis left unset on purpose — the code falls back toffmpegonPATH, which the image provides. - For the production sibling domains, set
AUTH_ALLOWED_HOSTSto both hosts,AUTH_TRUSTED_ORIGINSandWEBAUTHN_ORIGINSto their exact HTTPS origins, setAUTH_IP_ADDRESS_HEADERStocf-connecting-ip,x-forwarded-forwhen routing through Cloudflare, and set bothWEBAUTHN_RP_IDandAUTH_COOKIE_DOMAINto1a23.studio.
- Configure the environment variables in
- Node.js
- Install Node.js 24 LTS (24.15.0 or newer) and npm 12.0.0 or newer.
- Install dependencies:
npm install
- Build:
This is a topological Turborepo build. It also compiles the
npm run build
@lyricova/glyph-rendererRust/wasm crate (viawasm-pack, needing a stable Rust toolchain + thewasm32-unknown-unknowntarget) into its git-ignoredpkg/(WASM) andbuild/(JS) —@lyricova/jukeboximports these at build time and serves the WASM at runtime. Jukebox's owndev/build/startalso bootstrap the crate automatically; seedocs/development-and-build.md§4.1. This crate powers the experimental "Glyph Canvas (PoC)" lyrics renderer — seedocs/glyph-canvas-poc.mdfor what it is, the platform research behind it, and its known limitations. - Create a Lyricova user in the database as admin.
This prompts for a password interactively (hidden input, confirmed twice); pipe one in non-interactively instead with
npx --workspace @lyricova/api lyricova-admin user add --username <username> --email <email> --role admin --display-name <display-name>
--password-stdin < password.txtorecho "$PASSWORD" | npx --workspace @lyricova/api lyricova-admin user add ... --password-stdin. Seelyricova-adminCLI below for the full command reference and the legacy-auth migration recovery flow.
- Runtime
-
Runtime is supported by Docker.
-
Run it:
docker compose up -d(no local build — this pulls the published image) -
Lyricova blog is listening at port 59742 (
lyric) -
Jukebox is listening at port 58532 (
jukeb) -
There are three interchangeable variants of the app service. They all publish the same host ports, so run exactly one:
Service Profile Source of the app lyricova(default) Pulls ghcr.io/blueset/project-lyricova:nightlylyricova-buildbuildBuilds the same image from this checkout lyricova-bindmountbindmountRuntime OS deps only; serves a host build via a bind mount migratemigrateOne-shot: applies Drizzle migrations, then exits -
lyricova(default) runs the image published by the container workflow.:nightlyis a moving tag, sopull_policyisalways; Compose's default (missing) would otherwise pin you forever to the first image pulled. To deploy or roll back to a specific commit, pointLYRICOVA_IMAGEat an immutable tag:LYRICOVA_IMAGE=ghcr.io/blueset/project-lyricova:sha-1a2b3c4 docker compose up -d
-
lyricova-buildbuilds that same self-contained image locally: the multi-stageDockerfileinstalls the full dependency tree, provisions a Rust toolchain +wasm32-unknown-unknown, and runs the topologicalnpm run build(including the@lyricova/glyph-rendererwasm crate) inside the image, then ships the compiled output on top of annpm ci --omit=devtree. No host build is required, and no database is needed to build the image — nothing in the build graph queries the DB (GraphQL/OpenAPI codegen reads the committedpackages/api/schema.graphql, the API build istsc/eslint only, the drizzle pool connects lazily, and both Next apps fetch withcache: "no-store", so no route is prerendered with data). A database is still required at runtime and fordb:migrate. It is tagged separately (project-lyricova:local) so it never clobbers a pulled:nightly:docker compose --profile build up -d --build lyricova-build
This profile declares the
posthog_api_keybuild secret, so exportPOSTHOG_API_KEY(an empty value is fine — the build then skips sourcemap upload) before invoking it. The GitHub container workflow additionally uses Vercel Turborepo Remote Cache: BuildKit's GHCR cache reuses Docker layers, while Turbo restores unchanged package outputs after a source change invalidatesCOPY . .. Local Compose builds have no cache credential by default and fall back to Turbo's local cache. Seedocs/container-publishing.mdfor the OIDC setup required by the publish workflow. -
Note that the repo is deliberately not bind-mounted over
/appin either image-based mode: doing so would shadow the artifacts compiled into the image. -
lyricova-bindmountkeeps the previous workflow as a fallback. Its image carries runtime OS dependencies only and bind-mounts the repo, so you must build on the host first (npm install && npm run build) — including@lyricova/glyph-renderer'spkg//build/. Jukebox'sprestarthook verifies those prebuilt artifacts exist (and are not stale) and fails fast if the host build was skipped. Start it with:docker compose --profile bindmount up -d lyricova-bindmount
-
lyricova-admin (built from packages/api/src/admin) is a local, trusted
database tool, not a remote API client: it connects directly to the same
MySQL database and reads the same environment configuration (.env) as the
API server, so it must only be run by an operator who already has direct
database access (e.g. on the host/container running the API, or with a tunnel
to the production database). It performs every write through
accountService (packages/api/src/auth/accountService.ts) inside a DB
transaction, so identity/role changes and the "last active admin" guard stay
consistent even if the CLI is run concurrently from multiple places.
After npm run build --workspace @lyricova/api (or the root npm run build), invoke the compiled bin from the repo root with
npx --workspace @lyricova/api lyricova-admin, or run
node packages/api/dist/admin/index.js directly. The shorter
lyricova-admin spelling below refers to either form.
Usage: lyricova-admin <command> [options]
user add --username <name> --email <email> --role admin|guest
--display-name <name> [--display-username <name>] [--password-stdin]
user update (--username <name> | --id <n>) [--new-username <name>]
[--new-display-username <name>] [--email <email>]
[--role admin|guest] [--display-name <name>]
user list [--role admin|guest] [--include-disabled] [--include-deleted]
user disable (--username <name> | --id <n>) [--reason <text>]
user enable (--username <name> | --id <n>)
user reset-password (--username <name> | --id <n>) [--password-stdin]
user sessions list (--username <name> | --id <n>)
user sessions revoke (--username <name> | --id <n>) (--session-id <id> | --all) [--yes]
user passkeys list (--username <name> | --id <n>)
user passkeys revoke (--username <name> | --id <n>) (--passkey-id <id> | --all) [--yes]
auth audit
Global options: --json, -h/--help
Run lyricova-admin --help, lyricova-admin user --help, etc. for the same
reference at any time. Every command accepts --json for machine-readable
output and exits non-zero on error.
Passwords are never accepted as a plain CLI argument (that would leak
into shell history and ps). user add and user reset-password either
read a password from stdin with --password-stdin, or — on an interactive
terminal — prompt twice with echo disabled and require both entries to
match. Passwords must be 12–128 characters. reset-password (and
disable) also revoke all of the user's existing sessions, so old
browser/API sessions stop working immediately; passkeys are untouched by
those commands and must be revoked separately with user passkeys revoke
if needed. Destructive "revoke all" operations (sessions revoke --all,
passkeys revoke --all) prompt for confirmation on a TTY and require
--yes when run non-interactively (e.g. from a script).
The name Lyricova is a made up word from “lyrics” and “voca” (taken from Vocaloid) or “nova” (taking the meaning of new), which signifies a new way of working with Vocaloid lyrics. This project was previously known as Project Gy which is taken from its name in Chinese, Gē-yǔ (歌語). 歌 means songs, and 語 means to express, putting together, Gē-yǔ is making a sense of expressing oneself through songs and lyrics.
- Lyricize, a lyrics and quote collection site written in Django by @outloudvi. Heavily inspired by Project Lyricova (v1).
Project Lyricova: A lyrics blogging and music management suite for Vocaloid
Copyright (C) 2013–2026 Eana Hufwe
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.