Documentation

Install and run GainDrive.

Requirements

GainDrive Server is a single statically linked executable with no runtime dependencies except an optional ffmpeg (if you want to be able to stream transcoded audio/video). Binaries are provided for arm64 and x86_64 Linux.

  • A machine that can reach your music files. A Raspberry Pi, a NAS or a small VPS is plenty.
  • Disk space for two SQLite databases; these stay small relative to the music itself.
  • ffmpeg on PATH, only if you want on-the-fly transcoding.

Install

Linux binary

Download the release for your architecture, make it executable, and move it somewhere on your PATH.

$ curl -LO https://github.com/kpeeters/gaindrive/releases/latest/download/gaindrive-linux-x86_64
$ chmod +x gaindrive-linux-x86_64
$ sudo install gaindrive-linux-x86_64 /usr/local/bin/gaindrive

Build from source

You need a C++ compiler with C++20 support and CMake.

$ git clone https://github.com/kpeeters/gaindrive.git
$ cd gaindrive
$ cmake -B build -DCMAKE_BUILD_TYPE=Release
$ cmake --build build -j

First run

Before you can sign in, you need an account. Run GainDrive once with --add-user and --password to create one. This account will be an administrator account, but you can change that later in the web UI.

$ gaindrive --db ~/GainDrive --add-user alice --password s3cret

If you do not provide the user name or password, gaindrive will prompt you. After this is set, the server will exit.

Then start the server proper, pointing it at your database directory and your music. On the first start it creates both databases and begins an initial scan.

$ gaindrive --db ~/GainDrive --artist-root music=~/Music

Open http://localhost:4040 in a browser and sign in with the credentials set on the first run. Additional user accounts can be created in the web UI or by running the server with the --add-user flag again.

info
The audio/video scan runs in the background. Large collections keep populating while you browse, and metadata lookups continue after the file scan finishes.

Command-line options

OptionArgumentDescription
--hostIP addressInterface to listen on; use 0.0.0.0 for all interfaces. Default 127.0.0.1.
--portnumberPort to listen on. Default 4040.
--configpathPath to the config file. Default /etc/gaindrive.conf.
--dbpathPath to the music database file. Default /var/lib/gaindrive/gaindrive.db.
--user-dbpathPath to the user/state database. Derived from --db if not given.
--artist-rootname=pathLibrary root whose subdirectories are artists, i.e. organised as Artist/Album/Tracks. Repeatable.
--category-rootname=pathLibrary root whose subdirectories are categories, i.e. organised as Category/Name/Tracks. Repeatable.
--upload-rootname=pathRoot holding per-user personal uploads.
--upload-dirpathDirectory for uploaded archives. Default /tmp/gaindrive-uploads.
--transcode-cachepathDirectory for cached transcodes. Placed alongside --db if not given.
--transcode-cache-mbnumberTranscode cache size in MB; 0 disables caching. Default 1024.
--transcode-jobsnumberMaximum concurrent ffmpeg transcodes; 0 uses half the available cores. Default 0.
--scan-jobsnumberHow many files a library scan reads metadata from at once; 1 scans sequentially. 0 uses the core count capped at 8, which is the default — the cap is about the disk, so raise it past that only for solid-state storage.
--no-scann/aSkip the filesystem scan at startup.
--debugn/aPrint all API responses to stdout.
--add-usernameCreate a user account and exit.
--passwordpasswordPassword for the account created with --add-user.
-h, --helpn/aShow the list of options.
--versionn/aShow version and exit.

Configuration file

Everything that matters for a permanent install can also be set in a JSON configuration file, read from /etc/gaindrive.conf unless --config points elsewhere. This is the normal way to configure a service install: --install-service writes this file for you, and the unit it installs passes nothing else on the command line.

{
  "host": "0.0.0.0",
  "port": 4040,
  "db_path": "/var/lib/gaindrive/gaindrive.db",
  "user_db_path": "/var/lib/gaindrive/users.db",
  "roots": [
    { "name": "music",     "type": "artists",    "path": "/srv/music" },
    { "name": "audiobooks", "type": "categories", "path": "/srv/audiobooks" }
  ],
  "upload_dir": "/var/lib/gaindrive/uploads",
  "flat_multi_disc": false,
  "transcode_cache_dir": "/var/cache/gaindrive",
  "transcode_cache_mb": 1024,
  "transcode_jobs": 0,
  "scan_jobs": 0
}

Every key is optional; anything left out keeps its default. Each entry in roots takes a name, a path and a type of either artists or categories, matching --artist-root and --category-root. flat_multi_disc has no command-line equivalent and controls whether multi-disc sets are presented as a single flat album.

info
Command-line arguments win over the configuration file, so you can override a single setting for one run without editing the file. Library roots from the file are used only when no roots are given on the command line.

Structuring your library

GainDrive reads Artist and Album metadata from the folder structure, and uses ID3 tags where they exist to display track names and other track metadata. Either way it presents one unified library to every client, whether they use the tag-based browsing method or the file-based method.

A conventional layout gives the best results, and multi-disc sets are picked up automatically:

Music/
├── Artist Name/
│   ├── 1994 - Album Title/
│   │   ├── 01 First Track.flac
│   │   └── cover.jpg
│   └── 1998 - Box Set/
│       ├── Disc 1/
│       └── Disc 2/

Folders are watched with inotify, so added, moved and retagged files are reflected without a manual rescan. Artist and album metadata and artwork missing from your files are fetched from MusicBrainz and other sources.

GainDrive considers each file an audio/video track, but also knows about chapters inside such tracks. This enables you to e.g. navigate a concert video by song, or an audio book by chapter. You can search for chapters.

Running as a service

On Linux, GainDrive installs itself. Get it working in the foreground first, then re-run the same command with sudo and --install-service.

$ gaindrive --db ~/gaindrive/gaindrive.db
# creates the first account, then exits

$ gaindrive --db ~/gaindrive/gaindrive.db \
           --artist-root music=/srv/music
# check that it serves what you expect

$ sudo gaindrive --db ~/gaindrive/gaindrive.db \
                --artist-root music=/srv/music \
                --install-service
$ sudo systemctl start gaindrive

The last step writes /etc/gaindrive.conf from the options you just proved, writes /etc/systemd/system/gaindrive.service, and enables it for the next boot. It does not start the service, because the server from the previous step may still be holding the port.

The unit runs as the user who invoked sudo. That matters: GainDrive writes tags and cover art back into your library, so a service under its own system account would need write access to the whole music tree. Running as the person who already owns the files needs none of that.

--service-dry-run prints both documents and writes nothing. --service-user <name> names the account explicitly, which is needed under sudo -i and su. sudo gaindrive --uninstall-service removes the unit and leaves your config and database alone.

Change the unit with systemctl edit gaindrive rather than by editing the file: a drop-in survives the next --install-service, an edit to the unit itself does not.

macOS has no systemd. Use the Homebrew formula, whose launchd job is started with brew services start gaindrive.

Reverse proxy & HTTPS

Put GainDrive behind nginx, Apache or other proxy if you want it reachable from outside your network via https. For nginx:

server {
    server_name music.example.com;

    location / {
        proxy_pass http://127.0.0.1:4040;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 3600s; # for media streamed in real-time
        proxy_send_timeout 3600s; # for media streamed in real-time
    }
}

For Apache, enable proxy, proxy_http, proxy_wstunnel and headers, then add a virtual host:

<VirtualHost *:443>
    ServerName music.example.com

    ProxyPreserveHost On
    ProxyTimeout     3600  # for media streamed in real-time
    ProxyPass        / http://127.0.0.1:4040/
    ProxyPassReverse / http://127.0.0.1:4040/
    RequestHeader set X-Forwarded-Proto "https"
</VirtualHost>

Either way, run certbot afterwards to add the certificate and the HTTPS redirect.

public
Behind a proxy, tell GainDrive the origin your users actually reach it at: public_url: "https://music.example.com" in /etc/gaindrive.conf, or --public-url https://music.example.com on the command line. This is required so that gaindrive can construct the correct URLs for Chromecast sessions.

If the proxy runs on a different machine than GainDrive, also name it in trusted_proxies. GainDrive believes X-Forwarded-For only from addresses on that list (plus loopback).

shield
Do not add, replace or "fix" CORS headers at the proxy. GainDrive sends Access-Control-Allow-Origin: * on exactly the three endpoints a Chromecast fetches for itself (stream, getCaptions, hls) and deliberately nothing anywhere else (which is what stops any web page a user visits from reading API responses cross-origin). The same goes for the security headers (Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy): let them through unchanged.
cast
Casting from the server needs the Chromecast device to be able to reach the server directly on your local network. If your server is only reachable over a VPN, use the Android app's bridge mode instead. A Chromecast-built-in speaker that reaches GainDrive through the proxy is the case the timeouts above matter most for: it buffers a large part of a track, stops reading, and a 60-second default cuts it off.

Transcoding

When a client asks for a format or bitrate your files are not already in, GainDrive transcodes on the fly using ffmpeg. Enable a cache so a given transcode is only produced once.

$ gaindrive --artist-root music=/srv/music \
           --transcode-cache /var/cache/gaindrive

Without ffmpeg on PATH, GainDrive streams original files only; everything else keeps working.

Troubleshooting

Send me your questions...