A web-based LilyPond sheet music editor. https://hacklily.org/
  • TypeScript 84.7%
  • Rust 6.9%
  • Shell 3%
  • JavaScript 1.3%
  • Scheme 1.2%
  • Other 2.9%
Find a file
2026-07-15 09:51:18 -04:00
.forgejo/workflows lilypond-wasm renderer hardening: crash isolation, ok|failed|fatal taxonomy, and error-reporting parity 2026-07-15 07:31:01 -04:00
lilypond-wasm Properly support multiple pages in lilypond-wasm 2026-07-15 08:36:05 -04:00
server Cleanup copyright headers and notices 2026-07-12 11:19:32 -04:00
src Styling fixes from Blueprint/aphrodite -> tailwind migration 2026-07-15 09:51:18 -04:00
static force /wasm to render index.html p2 2026-07-15 09:16:23 -04:00
.gitattributes Do not track soundfonts as code in linguist 2026-07-02 07:50:12 -04:00
.gitignore Security: update deps (npm audit fix, dompurify 2→3, skipLibCheck, gitignore dist), unbreak dockerfile 2026-06-29 19:13:10 -04:00
.mailmap Correctly attribute work 2021-06-26 17:41:01 -04:00
.npmrc Add /wasm route 2026-07-13 12:29:58 -04:00
.oxfmtrc.json migrate from eslint/prettier/jest to oxlint/oxfmt/vitest 2026-07-10 11:52:36 -04:00
.oxlintrc.json Codemod 18 stateless class components to function components 2026-07-10 12:31:24 -04:00
components.json Add tailwind and shadcn scaffolding 2026-07-11 06:20:17 -04:00
index.html Cleanup copyright headers and notices 2026-07-12 11:19:32 -04:00
LICENSE.txt Cleanup copyright headers and notices 2026-07-12 11:19:32 -04:00
musicxml2ly.html Cleanup copyright headers and notices 2026-07-12 11:19:32 -04:00
package-lock.json Bump lilypond-wasm 2026-07-15 08:29:54 -04:00
package.json Bump lilypond-wasm 2026-07-15 08:29:54 -04:00
playground.html Add forwardRef to remaining Drawer components 2026-07-12 08:33:37 -04:00
README.md Fix sloppy sed replace in README.md 2026-07-15 07:44:38 -04:00
status.html Cleanup copyright headers and notices 2026-07-12 11:19:32 -04:00
tsconfig.json migrate from eslint/prettier/jest to oxlint/oxfmt/vitest 2026-07-10 11:52:36 -04:00
vite.config.ts lilypond-wasm renderer hardening: crash isolation, ok|failed|fatal taxonomy, and error-reporting parity 2026-07-15 07:31:01 -04:00
vitest.config.ts migrate from eslint/prettier/jest to oxlint/oxfmt/vitest 2026-07-10 11:52:36 -04:00

Hacklily

What is Hacklily?

Hacklily is an online sheet-music editor. You write LilyPond in a Monaco-powered editor in your browser and get a live rendered preview (SVG/PDF/PNG/MIDI). Scores can be saved to and published directly to GitHub, and MusicXML files can be imported via musicxml2ly.

Open Hacklily →

How it works

Hacklily has three parts:

  • Frontend (src/) — a React 18 SPA bundled with webpack. Monaco provides the editor; the preview talks to the renderer over a JSON-RPC 2.0 WebSocket. Three webpack entry points: index (the editor), status (a public server-status page), and musicxml2ly (the MusicXML importer).
  • Renderer (server/renderer/ and server/renderer-unstable/) — Docker images that build LilyPond from source (stable 2.26, unstable 2.27). Each container runs a warm LilyPond Scheme server (lily-server.scm) and a bash frame (render-impl.bash) that reads one JSON request per line on stdin and emits one JSON response per line on stdout.
  • Renderer server (server/renderer-server/, Rust) — a single binary that does three jobs: in serve mode it is the frontend-facing coordinator (WebSocket JSON-RPC, GitHub OAuth, a public get_status page) and the local render pool; in ws-worker mode it offers compute to a remote coordinator; in batch mode it renders test cases from a file. It keeps a pool of renderer containers warm, routes render requests (stable vs. unstable, picked from the score's \version), enforces an 8s render timeout, and tears down and replaces containers after each request for isolation. A coordinator with --stable-worker-count 0 --unstable-worker-count 0 runs in pure-coordinator mode, dispatching renders to remote ws-worker peers (and failing requests if none are attached). The legacy Qt5 coordinator that used to live in server/ws-server/ has been removed.

Running locally

You need Node and Docker.

# frontend (without GitHub integration)
npm install
npm start          # http://localhost:3000, talks to the remote render backend by default

# or, with the local backend:
npm start          # in one shell — REACT_APP_BACKEND_WS_URL defaults to the prod backend
cd server/renderer-server
cargo run -- \
  --stable-docker-tag hacklily-renderer \
  --unstable-docker-tag hacklily-renderer-unstable \
  --stable-worker-count 4 --unstable-worker-count 4 \
  --render-timeout-msec 8000 \
  serve --ws-port 2000 --github-client-id "" --github-secret ""

npm start:remote-backend runs the dev server pointed at the production render backend (wss://render.hacklily.org/rpc), so you don't need Docker for most frontend work.

Deployment

The serve coordinator listens on plain TCP (--ws-port, bound to 127.0.0.1 by default via --bind-address) and does not terminate TLS itself. In production it sits behind a TLS-terminating reverse proxy: the proxy presents the public wss://… certificate and forwards plain ws:// to the coordinator's port. The coordinator ignores the HTTP path (it upgrades any WebSocket handshake), so the proxy can route /rpc or any other path to it. The same reverse proxy that fronts the SPA can do this — just add a WebSocket-capable location block proxying to the coordinator port and ensure it passes through Upgrade/Connection headers.

The coordinator also supports an optional HTTP status endpoint (--http-status-port, e.g. 9990) that serves GET /status as JSON — the same data as the WebSocket get_status RPC but over plain HTTP, so monitoring scripts and load balancers don't need a WebSocket connection. The nginx site config in server/renderer-server/deploy/nginx/ proxies https://render.hacklily.org/status to it.

Graceful shutdown: send the process SIGTERM (this is what systemd, k8s, and docker stop send). The coordinator drains in-flight renders and exits 0; because a single render can take up to the render timeout (~8s), set the supervisor's termination grace period to exceed that so in-flight user renders aren't cut off mid-deploy. (SIGINT / Ctrl-C does the same thing for interactive use.)

A ready-to-use systemd user service (unit file, env template, install + update scripts, and docs) lives in server/renderer-server/deploy/. It runs the serve coordinator, restarts on crashes, pulls the published crate and renderer images from the public Codeberg registries (no credentials stored on the host), and updates with a single hacklily-renderer-update command that pulls the latest versions and restarts. See server/renderer-server/deploy/README.md for install and usage.

Status

Hacklily is stable and live at https://www.hacklily.org. The renderer is fully on the Rust renderer server, which now serves the frontend directly (the legacy Qt5 coordinator has been retired).

Contributing

Source and issue tracker: https://codeberg.org/jocelyn-stericker/hacklily

Contributions are welcome. Must be licensed AGPLv3-or-later.

License

GNU Affero General Public License version 3 or later