Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Nginx with Thruster to send file #398

Merged
merged 1 commit into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ gem "daemons", "~> 1.4.0"
# Optional support for postgresql as database
gem "pg", "~> 1.5.4"

# Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/]
gem "thruster", "~> 0.1.8", require: false

# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", "~> 1.18.0", require: false

Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ GEM
railties (>= 6.0.0)
stringio (3.1.1)
thor (1.3.2)
thruster (0.1.8)
timeout (0.4.1)
turbo-rails (1.5.0)
actionpack (>= 6.0.0)
Expand Down Expand Up @@ -428,6 +429,7 @@ DEPENDENCIES
standard (~> 1.25.0)
standard-rails
stimulus-rails (~> 1.3.4)
thruster (~> 0.1.8)
turbo-rails (~> 1.5.0)
tzinfo-data
wahwah (~> 1.6.0)
Expand Down
20 changes: 0 additions & 20 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,26 +126,6 @@ def render_json_error(error, status)
render json: {type: error.type, message: error.message}, status: status
end

def send_local_file(file_path, format, nginx_headers: {})
if BlackCandy.config.nginx_sendfile?
nginx_headers.each { |name, value| response.headers[name] = value }
send_file file_path

return
end

# Use Rack::Files to support HTTP range without nginx. see https://github.com/rails/rails/issues/32193
Rack::Files.new(nil).serving(request, file_path).tap do |(status, headers, body)|
self.status = status
self.response_body = body

headers.each { |name, value| response.headers[name] = value }

response.headers["Content-Type"] = Mime[format]
response.headers["Content-Disposition"] = "attachment"
end
end

def find_current_request_details
Current.ip_address = request.ip
Current.user_agent = request.user_agent
Expand Down
7 changes: 1 addition & 6 deletions app/controllers/concerns/stream_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@ module StreamConcern
end

def new
send_local_file @stream.file_path, @stream.format, nginx_headers: {
# Let nginx can get value of media_path dynamically in the nginx config
# when use X-Accel-Redirect header to send file.
"X-Media-Path" => Setting.media_path,
"X-Accel-Redirect" => File.join("/private_media", @stream.file_path.sub(File.expand_path(Setting.media_path), ""))
}
send_file @stream.file_path
end

private
Expand Down
4 changes: 1 addition & 3 deletions app/controllers/concerns/transcoded_stream_concern.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ def find_stream

def find_cache
return unless valid_cache?
send_local_file @stream.transcode_cache_file_path, @stream.format, nginx_headers: {
"X-Accel-Redirect" => File.join("/private_cache_media", @stream.transcode_cache_file_path.sub(Stream::TRANSCODE_CACHE_DIRECTORY.to_s, ""))
}
send_file @stream.transcode_cache_file_path
end

def valid_cache?
Expand Down
5 changes: 5 additions & 0 deletions bin/thrust
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env ruby
require "rubygems"
require "bundler/setup"

load Gem.bin_path("thruster", "thrust")
1 change: 0 additions & 1 deletion config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ module BlackCandy
has_config :queue_db_url
has_config :media_path
has_config :db_adapter, default: "sqlite"
has_config :nginx_sendfile, default: false
has_config :force_ssl, default: false
has_config :demo_mode, default: false

Expand Down
9 changes: 0 additions & 9 deletions config/nginx/sendfile.conf

This file was deleted.

2 changes: 1 addition & 1 deletion docker/production_start.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh

rails db:prepare
bundle exec puma -C config/puma.rb
./bin/thrust ./bin/rails server
186 changes: 186 additions & 0 deletions docs/README_EDGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<p align='center'>
<img alt='Black Candy logo' width='200' src='https://raw.githubusercontent.com/blackcandy-org/black_candy/master/app/assets/images/logo.svg'>
</p>

# Black Candy
[![CI](https://github.com/blackcandy-org/black_candy/actions/workflows/ci.yml/badge.svg)](https://github.com/blackcandy-org/black_candy/actions/workflows/ci.yml)
[![Coverage Status](https://coveralls.io/repos/github/blackcandy-org/blackcandy/badge.svg?branch=master)](https://coveralls.io/github/blackcandy-org/black_candy?branch=master)
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
![Docker Pulls](https://img.shields.io/docker/pulls/blackcandy/blackcandy)

![Screenshot](https://raw.githubusercontent.com/blackcandy-org/blackcandy/master/docs/images/screenshot_main.png)

Black Candy is a self-hosted music streaming server, your personal music center.

## Try The Demo

Please visit <https://demo.blackcandy.org> and use demo user (email: [email protected], password: foobar) to log in. And feel free to try it.

> [!NOTE]
> This demo user does not have administrator privileges. So you cannot experience all the features in Black Candy. And all music in the demo are from [Free Music Archive](https://freemusicarchive.org/). You can check their [licenses](https://github.com/blackcandy-org/blackcandy/blob/master/docs/demo_music_licenses.md).

## Installation

Black Candy uses docker image to install easily. You can run Black Candy like this.

```shell
docker run -p 3000:3000 ghcr.io/blackcandy-org/blackcandy:latest

# Or pull from Docker Hub.
docker run -p 3000:3000 blackcandy/blackcandy:latest
```

That's all. Now, you can access either http://localhost:3000 or http://host-ip:3000 in a browser, and use initial admin user to log in (email: [email protected], password: foobar).

## Upgrade

> [!IMPORTANT]
> If you upgrade to a major version, you need to read the upgrade guide carefully before upgrade. Because there are some breaking changes in a major version.
>
> - See [V3 Upgrade](https://github.com/blackcandy-org/blackcandy/blob/master/docs/v3_upgrade.md) for upgrade from V2 release.
> - See [Edge Upgrade](https://github.com/blackcandy-org/blackcandy/blob/master/docs/edge_upgrade.md) for upgrade from edge release to latest stable release.

Upgrade Black Candy is pull new image from remote. Then remove an old container and create a new one.

```shell
docker pull ghcr.io/blackcandy-org/blackcandy:latest
docker stop <your_blackcandy_container>
docker rm <your_blackcandy_container>
docker run <OPTIONS> ghcr.io/blackcandy-org/blackcandy:latest
```

With docker compose, you can upgrade Black Candy like this:

```shell
docker pull ghcr.io/blackcandy-org/blackcandy:latest
docker-compose down
docker-compose up
```

## Mobile Apps

Black Candy mobile apps are available in the following app stores:

[<img src="https://raw.githubusercontent.com/blackcandy-org/ios/master/images/appstore_badge.png" alt="Get it on App Store" height="50">](https://apps.apple.com/app/blackcandy/id6444304071)
[<img src="https://raw.githubusercontent.com/blackcandy-org/android/master/images/fdroid_badge.png" alt="Get it on F-Droid" height="50">](https://f-droid.org/packages/org.blackcandy.android/)


For Android app, you can also download APK from [GitHub Release](https://github.com/blackcandy-org/android/releases/latest)

## Configuration

### Port Mapping

Black Candy exports the 3000 port. If you want to be able to access it from the host, you can use the `-p` option to map the port.

```shell
docker run -p 3000:3000 ghcr.io/blackcandy-org/blackcandy:latest
```

### Media Files Mounts

You can mount media files from host to container and use `MEDIA_PATH` environment variable to set the media path for black candy.

```shell
docker run -v /media_data:/media_data -e MEDIA_PATH=/media_data ghcr.io/blackcandy-org/blackcandy:latest
```

### Use PostgreSQL As Database

Black Candy use SQLite as database by default. Because SQLite can simplify the process of installation, and it's an ideal choice for self-hosted small server. If you think SQLite is not enough, or you are using some cloud service like heroku to host Black Candy, you can also use PostgreSQL as database.

```shell
docker run -e DB_ADAPTER=postgresql -e DB_URL=postgresql://yourdatabaseurl ghcr.io/blackcandy-org/blackcandy:latest
```

### How to Persist Data

All the data that need to persist in Black Candy are stored in `/app/storage`, So you can mount this directory to host to persist data.

```shell
mkdir storage_data

docker run -v ./storage_data:/app/storage ghcr.io/blackcandy-org/blackcandy:latest
```

### Logging

Black Candy logs to `STDOUT` by default. So if you want to control the log, Docker already supports a lot of options to handle the log in the container. See: https://docs.docker.com/config/containers/logging/configure/.

## Environment Variables

| Name | Default | Description |
| --- | --- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| DB_URL | | The URL of PostgreSQL database. You must set this environment variable if you use PostgreSQL as database. |
| CABLE_DB_URL | | The URL of Pub/Sub database. You must set this environment variable if you use PostgreSQL as database. |
| QUEUE_DB_URL | | The URL of background job database. You must set this environment variable if you use PostgreSQL as database. |
| CACHE_DB_URL | | The URL of cache database. You must set this environment variable if you use PostgreSQL as database. |
| MEDIA_PATH | | You can use this environment variable to set media path for Black Candy, otherwise you can set media path in settings page. |
| DB_ADAPTER | "sqlite" | There are two adapters are supported, "sqlite" and "postgresql". |
| SECRET_KEY_BASE | | When the SECRET_KEY_BASE environment variable is not set, Black candy will generate SECRET_KEY_BASE environment variable every time when service start up. This will cause old sessions invalid, You can set your own SECRET_KEY_BASE environment variable on docker service to avoid it. |
| FORCE_SSL | false | Force all access to the app over SSL. |
| DEMO_MODE | false | Whether to enable demo mode, when demo mode is on, all users cannot access administrator privileges, even user is admin. And also users cannot change their profile. |

## Edge Version

The edge version of Black Candy base on master branch, which means it's not stable, you may encounter data loss or other issues. However, I don't recommend normal user using an edge version. But if you are a developer who wants to test or contribute to Black Candy, you can use the edge version.

```shell
docker pull ghcr.io/blackcandy-org/blackcandy:edge
```

## Development

### Requirements

- Ruby 3.3
- Node.js 20
- libvips
- FFmpeg

Make sure you have installed all those dependencies.

### Install gem dependencies

```shell
bundle install
```

### Install JavaScript dependencies

```shell
npm install
```

### Database Configuration

```shell
rails db:prepare
rails db:seed
```

### Start all services

After you’ve set up everything, now you can run `./bin/dev` to start all services you need to develop.
Then visit <http://localhost:3000> use initial admin user to log in (email: [email protected], password: foobar).

### Running tests

```shell
# Running all test
$ rails test:all

# Running lint
$ rails lint:all
```

## Integrations

Black Candy support get artist and album image from Discogs API. You can create an API token from Discogs and set Discogs token on Setting page to enable it.

## Sponsorship

This project is supported by:

<a href="https://www.digitalocean.com/"><img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="200px"></a>
<a href="https://www.jetbrains.com/community/opensource"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_square.svg"></a>
9 changes: 0 additions & 9 deletions test/controllers/api/v1/stream_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ class Api::V1::StreamControllerTest < ActionDispatch::IntegrationTest
assert_response :success
end

test "should set header for nginx send file" do
with_env("NGINX_SENDFILE" => "true") do
get new_api_v1_stream_url(song_id: songs(:mp3_sample).id), headers: api_token_header(@user)

assert_equal Setting.media_path, @response.get_header("X-Media-Path")
assert_equal "/private_media/artist1_album2.mp3", @response.get_header("X-Accel-Redirect")
end
end

test "should respond file data" do
get new_api_v1_stream_url(song_id: songs(:mp3_sample).id), headers: api_token_header(@user)
assert_equal binary_data(file_fixture("artist1_album2.mp3")), response.body
Expand Down
13 changes: 0 additions & 13 deletions test/controllers/api/v1/transcoded_stream_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,19 +69,6 @@ def close_tmp_cache_file
end
end

test "should send cached transcoded stream file when found cache and send file with nginx" do
Stream.stub(:new, @stream_mock) do
get new_api_v1_transcoded_stream_url(song_id: songs(:flac_sample).id), headers: api_token_header(@user)
assert_response :success

with_env("NGINX_SENDFILE" => "true") do
get new_api_v1_transcoded_stream_url(song_id: songs(:flac_sample).id), headers: api_token_header(@user)
assert_equal "/private_cache_media#{@stream_mock.transcode_cache_file_path}", @response.get_header("X-Accel-Redirect")
assert_equal "audio/mpeg", @response.get_header("Content-Type")
end
end
end

test "should regenerate new cache when cache is invalid" do
Stream.stub(:new, @stream_mock) do
stream = Stream.new(songs(:flac_sample))
Expand Down
9 changes: 0 additions & 9 deletions test/lib/black_candy/config_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,6 @@ class BlackCandy::ConfigTest < ActiveSupport::TestCase
end
end

test "should get nginx_sendfile value as a boolean" do
assert_nil ENV["NGINX_SENDFILE"]
assert_not BlackCandy.config.nginx_sendfile?

with_env("NGINX_SENDFILE" => "true") do
assert BlackCandy.config.nginx_sendfile?
end
end

test "should raise error when database_adapter is not supported" do
with_env("DB_ADAPTER" => "invalid_adapter") do
assert_raises(BlackCandy::Config::ValidationError) do
Expand Down