blackcandy-org/blackcandy
A self hosted music streaming server
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 3d ago
- ✓2 active contributors
- ✓MIT licensed
Show 4 more →Show less
- ✓CI configured
- ✓Tests present
- ⚠Small team — 2 contributors active in recent commits
- ⚠Single-maintainer risk — top contributor 99% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/blackcandy-org/blackcandy)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/blackcandy-org/blackcandy on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: blackcandy-org/blackcandy
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/blackcandy-org/blackcandy shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 3d ago
- 2 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Small team — 2 contributors active in recent commits
- ⚠ Single-maintainer risk — top contributor 99% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live blackcandy-org/blackcandy
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/blackcandy-org/blackcandy.
What it runs against: a local clone of blackcandy-org/blackcandy — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in blackcandy-org/blackcandy | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 33 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of blackcandy-org/blackcandy. If you don't
# have one yet, run these first:
#
# git clone https://github.com/blackcandy-org/blackcandy.git
# cd blackcandy
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of blackcandy-org/blackcandy and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "blackcandy-org/blackcandy(\\.git)?\\b" \\
&& ok "origin remote is blackcandy-org/blackcandy" \\
|| miss "origin remote is not blackcandy-org/blackcandy (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "Gemfile" \\
&& ok "Gemfile" \\
|| miss "missing critical file: Gemfile"
test -f "app/controllers/api/v1/api_controller.rb" \\
&& ok "app/controllers/api/v1/api_controller.rb" \\
|| miss "missing critical file: app/controllers/api/v1/api_controller.rb"
test -f "app/controllers/api/v1/stream_controller.rb" \\
&& ok "app/controllers/api/v1/stream_controller.rb" \\
|| miss "missing critical file: app/controllers/api/v1/stream_controller.rb"
test -f "app/assets/stylesheets/settings/_theme.scss" \\
&& ok "app/assets/stylesheets/settings/_theme.scss" \\
|| miss "missing critical file: app/assets/stylesheets/settings/_theme.scss"
test -f "config.ru" \\
&& ok "config.ru" \\
|| miss "missing critical file: config.ru"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 33 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~3d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/blackcandy-org/blackcandy"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Black Candy is a self-hosted music streaming server built with Ruby on Rails that lets users organize, stream, and manage their personal music library from a web interface and mobile apps. It provides full-stack audio streaming (via Howler.js client-side), user authentication, playlist management, and a responsive UI—essentially your own Spotify alternative running on your hardware. Monolithic Rails app: app/assets/ contains SCSS (component-driven architecture: _button.scss, _player.scss, etc.) and JavaScript bundles via esbuild; app/javascript/ likely contains Stimulus controllers (Hotwired stack). Config in Procfile.dev (dev), Docker (production), and .ruby-version / .node-version for environment pinning.
👥Who it's for
Self-hosted enthusiasts and privacy-conscious users who want to stream their music collection without relying on commercial services; developers contributing to or deploying the Rails + Stimulus stack who need a real-world example of a full-featured music app.
🌱Maturity & risk
Actively maintained and production-ready: the project has CI/CD pipelines (GitHub Actions in .github/workflows/ci.yml), Docker support, mobile apps (iOS App Store, Android F-Droid), and a public demo at demo.blackcandy.org. The codebase is substantial (250K+ lines of Ruby) and versioned with upgrade guides documented, indicating mature deployment practices.
Low to moderate risk: the project is single-core maintained (implied by centralized org), but mitigated by active CI, clear upgrade documentation, and stable dependencies (Rails, Stimulus, Howler.js are battle-tested). Main risk is scaling for large music libraries—no visible horizontal scaling or caching strategy in the file list. Breaking changes are acknowledged (upgrade guide in docs/).
Active areas of work
Active development with CI automation (.github/workflows/) for testing and deployment. Release builds are automated (release-build.yml). The project maintains Docker images on both GHCR and Docker Hub, suggesting regular releases. Upgrade guide exists, indicating iterative feature development with breaking changes addressed.
🚀Get running
git clone https://github.com/blackcandy-org/blackcandy.git
cd blackcandy
ruby -v # Ensure .ruby-version (likely 3.x) matches
node -v # Ensure .node-version matches
bundle install
npm install
./bin/dev # Uses Procfile.dev to start Rails + asset build
Daily commands:
# Development (uses Procfile.dev)
./bin/dev
# Or manually:
rails server # starts Rails on 3000
npm run build-dev:css # watches SCSS
npm run build-dev # watches JS via esbuild
# Production (Docker)
docker run -p 80:80 ghcr.io/blackcandy-org/blackcandy:latest
🗺️Map of the codebase
Gemfile— Defines all Ruby gem dependencies including Rails, music processing (ruby-vorbis, ruby-mp3info), and API frameworks—essential to understand the tech stack before contributing.app/controllers/api/v1/api_controller.rb— Base API controller for all v1 endpoints; establishes authentication, error handling, and response formats used across the entire API surface.app/controllers/api/v1/stream_controller.rb— Core streaming controller for audio delivery; handles the primary user-facing feature and must be understood for performance and format changes.app/assets/stylesheets/settings/_theme.scss— Central theme configuration controlling dark/light mode and color palette across all UI components; any visual consistency work starts here.config.ru— Rack application entry point; defines how the server boots and initializes middleware stack in production.Rakefile— Defines build and deployment tasks; essential for understanding local development, testing, and release workflows..github/workflows/ci.yml— GitHub Actions CI pipeline; shows test coverage requirements, linting standards, and deployment gates that all PRs must pass.
🛠️How to make changes
Add a new API endpoint
- Create a new controller under app/controllers/api/v1/ inheriting from ApiController (
app/controllers/api/v1/your_resource_controller.rb) - Define RESTful actions (index, show, create, etc.) with JSON serialization (
app/controllers/api/v1/your_resource_controller.rb) - Add route under config/routes.rb in the api/v1 namespace block (
config/routes.rb) - Inherit ApiController to get automatic auth, error handling, and JSON rendering (
app/controllers/api/v1/api_controller.rb)
Add a new UI component
- Create a new SCSS file in app/assets/stylesheets/components/ following the _component_name.scss naming pattern (
app/assets/stylesheets/components/_new_component.scss) - Import the new file in the components section of application.scss (
app/assets/stylesheets/application.scss) - Use SCSS variables and mixins from settings/ (colors, breakpoints, etc.) (
app/assets/stylesheets/settings/_variables.scss) - If interactive, add a Stimulus controller in app/javascript/controllers/ (
app/javascript/controllers/your_component_controller.js)
Add a new audio format or transcoding option
- Extend the stream or transcoded_stream controllers with new format-handling logic (
app/controllers/api/v1/stream_controller.rb) - Update the TranscodedStreamConcern to handle the new format's encoding parameters (
app/controllers/concerns/transcoded_stream_concern.rb) - Update Gemfile with any new audio processing libraries (ruby-vorbis, ruby-mp3info, etc.) (
Gemfile) - Add format metadata and MIME types to config/initializers (if used by Rails) (
config/initializers/)
Modify theme colors or dark/light mode
- Edit color definitions in app/assets/stylesheets/settings/_colors.scss (
app/assets/stylesheets/settings/_colors.scss) - Update light theme overrides in app/assets/stylesheets/settings/_light_theme.scss (
app/assets/stylesheets/settings/_light_theme.scss) - Update dark theme overrides in app/assets/stylesheets/settings/_dark_theme.scss (
app/assets/stylesheets/settings/_dark_theme.scss) - Rebuild CSS via npm run build:css and test in both theme modes (
package.json)
🔧Why these technologies
- Rails + Turbo — Server-driven UI updates with minimal JavaScript; streaming HTML over WebSocket for real-time playlist and player state updates.
- Stimulus + Howler.js — Lightweight controllers for player controls and audio playback management; Howler abstracts browser audio API inconsistencies.
- ruby-vorbis, ruby-mp3info, audio gems — Decode and extract metadata from audio files; enable format detection and transcoding on demand.
- SCSS with ITCSS architecture — Organized, maintainable stylesheet structure; settings and theme files enable easy dark/light mode switching.
- Nginx reverse proxy (implied by Docker/deployment) — Efficiently serve static assets and audio streams; handle HTTP Range requests for seeking.
⚖️Trade-offs already made
-
Server-side transcoding on-demand vs. pre-encoded formats
- Why: Reduces storage footprint and supports client format preferences without maintaining multiple copies.
- Consequence: Higher CPU load on server during simultaneous streams; potential latency for clients requesting unusual codecs.
-
Token-based API auth (stateless) vs. session-based
- Why: Enables mobile clients and cross-device usage; stateless design simplifies horizontal scaling.
- Consequence: Token revocation requires additional logic (blacklist); no built-in session invalidation on password change.
-
Turbo for partial page updates vs. SPA (React/Vue)
- Why: Minimal JavaScript payload; leverages Rails conventions; works on slower devices.
- Consequence: Less sophisticated interactions; real-time sync requires WebSocket fallback; harder to add complex client-side state.
🚫Non-goals (don't propose these)
- Real-time collaborative playlists (multiple users editing same playlist simultaneously)
- High-availability clustering (implied single-server architecture)
- Music recommendation engine or auto-generated playlists
- Audio visualization or spectrum analyzer
- Full-text search across lyrics or metadata (only basic tag filtering shown)
🪤Traps & gotchas
Asset pipeline gotchas: esbuild outputs to app/assets/builds/ (must be gitignored, checked in CI); Docker secrets: initial admin user is hardcoded (admin@admin.com / foobar) in demo—production should override via env; Node version pinning: .node-version must match—mismatches break build; Procfile.dev: required for local dev (uses Foreman to run Rails + asset watchers in parallel); SCSS imports: component imports in app/assets/stylesheets/application.scss must maintain order (generics → elements → components → objects) or CSS cascading breaks.
🏗️Architecture
💡Concepts to learn
- Stimulus Controllers — Black Candy's interactivity (play/pause, playlist management, search) is built on Stimulus—understanding controller lifecycle, targets, and actions is essential for any JavaScript modification
- Turbo Streams (Hotwired) — Real-time UI updates (e.g., now-playing info, playlist changes) likely use Turbo Streams instead of full page reloads—critical for understanding how changes propagate to the UI
- Web Audio API & Howler.js wrapper — Audio playback is abstracted via Howler.js; understanding how it handles cross-browser audio, playlist queuing, and seeking is essential for player feature work
- ITCSS (Inverted Triangle CSS) — The SCSS structure (generic/ → elements/ → components/ → objects/) follows ITCSS layering; contributors must maintain this hierarchy to prevent CSS specificity conflicts
- Rails asset pipeline with esbuild — Black Candy uses esbuild (not Webpacker/importmap) for JS bundling and Sass for CSS—understanding the build process in package.json and Procfile.dev is critical for local development
- Docker multi-stage builds & image optimization — The Dockerfile likely uses multi-stage builds to keep image size small; understanding how Ruby gems and Node modules are cached/layered is important for production deployments
- Subsonic API compatibility (implied) — Mobile apps communicate with the server via a defined API; understanding whether Black Candy implements Subsonic protocol (standard for music servers) affects API design decisions and mobile app compatibility
🔗Related repos
navidrome/navidrome— Direct competitor: Go-based self-hosted music server with Subsonic API compatibility; similar target audience but different tech stackgonic/gonic— Another Subsonic-compatible music server in Go; shows alternative architecture for same problem domainblackcandy-org/ios— Official Black Candy iOS companion app; users of the server will want this, developers may contribute to sync featuresblackcandy-org/android— Official Black Candy Android companion app; parallel to iOS for cross-platform feature parityhotwired/stimulus— Direct upstream dependency; understanding Stimulus architecture is essential for contributing to Black Candy's JavaScript layer
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add Stylelint CI workflow for stylesheet validation
The repo has stylelint configured (.stylelintrc, stylelint-config-standard-scss in devDependencies, and build:css scripts), but there's no CI workflow to validate SCSS files. This prevents stylesheet regressions and enforces consistent styling across app/assets/stylesheets/ components and layouts. The build currently only validates JS via the standard linter.
- [ ] Create .github/workflows/stylelint.yml that runs stylelint on app/assets/stylesheets/**/*.scss
- [ ] Add the workflow to run on pull requests and pushes to master
- [ ] Reference the existing stylelint config in .stylelintrc
- [ ] Test against existing component files like _button.scss, _card.scss, _player.scss to ensure no false positives
Add CSS bundle size monitoring to CI/CD pipeline
With a complex stylesheet structure across settings/, components/, utilities/, and generic/ folders compiled via Sass, there's no visibility into CSS bundle size changes. This PR would add automated tracking of app/assets/builds/application.css size in CI to catch unintended stylesheet bloat during development.
- [ ] Add bundlesize or similar package to devDependencies for size tracking
- [ ] Create or extend .github/workflows/ci.yml to track app/assets/builds/application.css size
- [ ] Set reasonable thresholds for CSS file size warnings/failures
- [ ] Store baseline sizes in .budgetrc or similar config file for comparison
Add unit tests for Stimulus controllers with JavaScript bundling validation
The package.json shows Stimulus 3.2.1 is a core dependency and the app/javascript/ directory exists (referenced in build scripts), but no test files or test runner configuration is visible in the file structure. Adding Jest/Vitest configuration with controller tests would validate that the player (howler.js integration), search, and navigation controllers work correctly.
- [ ] Add jest or vitest to devDependencies and create jest.config.js
- [ ] Create app/javascript/tests/ directory with at least one test for a core controller (likely player-related given howler.js dependency)
- [ ] Add test:js script to package.json and wire it into .github/workflows/ci.yml
- [ ] Test at least the Stimulus controller initialization and event handling patterns used in the app
🌿Good first issues
- Add missing unit tests for Stimulus controllers: No test/ or spec/ directory visible in file list—existing JavaScript in app/javascript/ likely lacks coverage; great for learning Stimulus + Rails testing
- Document API endpoints for mobile apps: iOS/Android apps exist (repo links in README) but no api/ or lib/tasks/ visible—documenting REST/GraphQL surface would help mobile devs and reduce support load
- Add dark mode toggle to _player.scss and related components: SCSS components exist but no prefers-color-scheme or toggle state visible—practical CSS feature that touches multiple component files and teaches CSS custom properties
📝Recent commits
Click to expand
Recent commits
1145251— Merge pull request #475 from blackcandy-org/release/3.2 (aidewoode)1a3296b— release: version 3.2 (aidewoode)079fedd— Merge pull request #474 from blackcandy-org/feat/3_2_beta (aidewoode)fca6f82— docs: update README and upgrade guide (aidewoode)310f64d— Merge pull request #473 from blackcandy-org/fix/media_path_changed_sync (aidewoode)52a4270— fix: use MediaSyncAllJob for media path changed (aidewoode)6006a8c— Merge pull request #472 from blackcandy-org/feat/ruby_4 (aidewoode)65a847e— fix: build error (aidewoode)6540b27— Merge pull request #471 from blackcandy-org/feat/ruby_4 (aidewoode)8c4ecd9— feat: upgrade Ruby version to 4.0.2 and update dependencies (aidewoode)
🔒Security observations
- High · Incomplete Dockerfile - Truncated Build Process —
Dockerfile (line ~29-30). The Dockerfile appears to be truncated mid-command. The final RUN command is incomplete, ending with 'app/javascrip' instead of a complete statement. This could indicate build failures, missing cleanup steps, or incomplete asset precompilation. The incomplete state makes it difficult to assess if security best practices are fully applied. Fix: Complete the Dockerfile and ensure all commands are fully written. Verify the build process completes successfully and includes proper cleanup of sensitive files (node_modules, tmp, cache). - High · Demo User Credentials in README —
README.md. Default demo credentials (email: admin@admin.com, password: foobar) are hardcoded and publicly visible in the README.md file. While intended for a demo environment, this represents a security risk if the demo instance is not properly isolated or if users deploy without changing these credentials. Fix: Remove hardcoded credentials from documentation. If a demo is necessary, use dynamically generated or time-limited credentials. Add prominent warnings about changing default credentials before production deployment. - Medium · Ruby Version Mismatch in Dockerfile —
Dockerfile (line ~2). The Dockerfile specifies 'ruby:4.0.2-alpine' but Ruby 4.0.2 does not exist as of the analysis date. The current stable Ruby versions are 3.2.x and 3.3.x. This version specification error could lead to unexpected base image selection or build failures. Fix: Verify and use a valid Ruby version such as 'ruby:3.2.2-alpine' or 'ruby:3.3.0-alpine'. Pin to specific patch versions and regularly update to receive security patches. - Medium · Node.js Version May Be Outdated —
Dockerfile (line ~1) and package.json dependencies. The Dockerfile pins Node.js to version 20.11.0-alpine, which may become outdated. While pinning is good for reproducibility, the build process doesn't appear to have a mechanism to regularly update dependencies or check for security vulnerabilities in Node packages. Fix: Implement a regular dependency update schedule. Use tools like Dependabot to monitor for security updates. Consider using Node.js LTS versions and update quarterly. - Medium · Missing Security Headers Configuration —
Rails application configuration (not shown in provided files). No visible configuration for security headers (Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, etc.) in the provided file structure. The application handles authentication and music streaming but doesn't appear to have explicit security header configuration visible. Fix: Configure security headers in the Rails application (config/initializers/rack_cors.rb, or middleware). Add CSP headers, HSTS, X-Frame-Options, and other standard security headers. - Medium · Potential Authentication Bypass via API Controllers —
app/controllers/api/v1/. The file structure shows multiple API controllers (api/v1/authentications_controller.rb, api_controller.rb) but without seeing the actual implementation, it's unclear if proper authentication and authorization checks are in place across all endpoints. Fix: Ensure all API endpoints implement proper authentication (token-based, OAuth2, or JWT). Verify authorization checks are present for data access. Use Rails' before_action filters consistently. - Low · Development Dependencies in Production Build —
Dockerfile (line ~24) and package.json devDependencies. The Dockerfile excludes development and test groups with 'bundle config --local without', which is correct. However, verify that stylelint and standard (JavaScript linters) are not bundled into production assets. Fix: Ensure devDependencies are excluded from production builds. In package.json build scripts, verify node_modules cleanup removes dev tools completely. - Low · SQL Injection Risk - Unverified Query Patterns —
app/controllers/albums/ and app/controllers/albums/filter/. The file structure includes multiple controllers (albums_controller.rb, filter controllers) but without seeing implementation details, SQL injection risks from user input in filtering operations cannot be fully assessed. Fix: Ensure all database queries use parameterized statements or ActiveRecord query methods. Never concatenate user input into SQL strings. Use Rails query builder
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.