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.
ffmpeg on PATH, only if you want on-the-fly transcoding.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
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
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.
| Option | Argument | Description |
|---|---|---|
--host | IP address | Interface to listen on; use 0.0.0.0 for all interfaces. Default 127.0.0.1. |
--port | number | Port to listen on. Default 4040. |
--config | path | Path to the config file. Default /etc/gaindrive.conf. |
--db | path | Path to the music database file. Default /var/lib/gaindrive/gaindrive.db. |
--user-db | path | Path to the user/state database. Derived from --db if not given. |
--artist-root | name=path | Library root whose subdirectories are artists, i.e. organised as Artist/Album/Tracks. Repeatable. |
--category-root | name=path | Library root whose subdirectories are categories, i.e. organised as Category/Name/Tracks. Repeatable. |
--upload-root | name=path | Root holding per-user personal uploads. |
--upload-dir | path | Directory for uploaded archives. Default /tmp/gaindrive-uploads. |
--transcode-cache | path | Directory for cached transcodes. Placed alongside --db if not given. |
--transcode-cache-mb | number | Transcode cache size in MB; 0 disables caching. Default 1024. |
--transcode-jobs | number | Maximum concurrent ffmpeg transcodes; 0 uses half the available cores. Default 0. |
--scan-jobs | number | How 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-scan | n/a | Skip the filesystem scan at startup. |
--debug | n/a | Print all API responses to stdout. |
--add-user | name | Create a user account and exit. |
--password | password | Password for the account created with --add-user. |
-h, --help | n/a | Show the list of options. |
--version | n/a | Show version and exit. |
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.
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.
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.
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_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).
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.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.