-
-
Notifications
You must be signed in to change notification settings - Fork 15.1k
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
Add "Lightweight Music Server" application + service #147408
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
{ config, lib, pkgs, ... }: | ||
|
||
with lib; | ||
|
||
let | ||
cfg = config.services.lms; | ||
settingsFormat = pkgs.formats.toml {}; | ||
in | ||
{ | ||
options.services.lms = { | ||
enable = mkEnableOption "LMS (Lightweight Music Server). A self-hosted, music streaming server"; | ||
|
||
stateDir = mkOption { | ||
type = types.path; | ||
default = "/var/lib/lms"; | ||
description = '' | ||
The directory where lms uses as a working directory to store its state. | ||
If left as the default value this directory will automatically be | ||
created before the lms server starts, otherwise the sysadmin is | ||
responsible for ensuring the directory exists with appropriate ownership | ||
and permissions. | ||
''; | ||
}; | ||
|
||
virtualHost = mkOption { | ||
type = types.nullOr types.str; | ||
default = null; | ||
description = '' | ||
Name of the nginx virtualhost to setup as reverse proxy. If null, do | ||
not setup any virtualhost. | ||
''; | ||
}; | ||
|
||
user = mkOption { | ||
type = types.str; | ||
default = "lms"; | ||
description = '' | ||
User account under which LMS runs. If left as the default value this | ||
user will automatically be created on system activation, otherwise the | ||
sysadmin is responsible for ensuring the user exists before the lms | ||
service starts. | ||
''; | ||
}; | ||
|
||
settings = mkOption { | ||
type = types.submodule { | ||
freeformType = settingsFormat.type; | ||
options = { | ||
listen-addr = mkOption { | ||
type = types.str; | ||
default = "127.0.0.1"; | ||
description = '' | ||
The address on which to bind LMS. | ||
''; | ||
}; | ||
|
||
listen-port = mkOption { | ||
type = types.port; | ||
default = 5082; | ||
description = '' | ||
The port on which LMS will listen to. | ||
''; | ||
}; | ||
|
||
api-subsonic = mkOption { | ||
type = types.bool; | ||
default = true; | ||
description = '' | ||
Enable the Subsonic API. | ||
''; | ||
}; | ||
}; | ||
}; | ||
default = {}; | ||
description = '' | ||
Configuration for LMS, see | ||
<link xlink:href="https://github.com/epoupon/lms/blob/master/conf/lms.conf"/> | ||
for full list of supported values. | ||
''; | ||
}; | ||
}; | ||
|
||
config = mkIf cfg.enable { | ||
services.lms.settings = { | ||
working-dir = cfg.stateDir; | ||
behind-reverse-proxy = cfg.virtualHost != null; | ||
docroot = "${pkgs.lms}/share/lms/docroot/;/resources,/css,/images,/js,/favicon.ico"; | ||
approot = "${pkgs.lms}/share/lms/approot"; | ||
cover-max-file-size = mkDefault 10; | ||
cover-max-cache-size = mkDefault 30; | ||
}; | ||
|
||
systemd.services.lms = { | ||
description = "Lightweight Music Server"; | ||
after = [ "network.target" ]; | ||
wantedBy = [ "multi-user.target" ]; | ||
|
||
serviceConfig = mkMerge [ | ||
{ | ||
ExecStart = | ||
let lmsConfig = settingsFormat.generate "lms.conf" cfg.settings; | ||
in "${pkgs.lms}/bin/lms ${lmsConfig}"; | ||
Restart = "on-failure"; | ||
RestartSec = 5; | ||
User = cfg.user; | ||
Group = "lms"; | ||
WorkingDirectory = cfg.stateDir; | ||
UMask = "0022"; | ||
|
||
NoNewPrivileges = true; | ||
ProtectSystem = true; | ||
ProtectHome = true; | ||
PrivateTmp = true; | ||
PrivateDevices = true; | ||
ProtectClock = true; | ||
ProtectKernelTunables = true; | ||
ProtectKernelModules = true; | ||
ProtectKernelLogs = true; | ||
ProtectControlGroups = true; | ||
} | ||
(mkIf (cfg.stateDir == "/var/lib/lms") { | ||
StateDirectory = "/var/lib/lms"; | ||
}) | ||
]; | ||
}; | ||
|
||
# from: https://github.com/epoupon/lms#reverse-proxy-settings | ||
services.nginx = mkIf (cfg.virtualHost != null) { | ||
enable = true; | ||
virtualHosts.${cfg.virtualHost} = { | ||
locations."/" = { | ||
proxyPass = "http://${cfg.settings.listen-addr}:${toString cfg.settings.listen-port}"; | ||
extraConfig = '' | ||
proxy_set_header Host $host; | ||
proxy_set_header X-Real-IP $remote_addr; | ||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; | ||
proxy_set_header X-Forwarded-Proto $scheme; | ||
proxy_read_timeout 120; | ||
''; | ||
}; | ||
extraConfig = '' | ||
proxy_request_buffering off; | ||
proxy_buffering off; | ||
proxy_buffer_size 4k; | ||
proxy_read_timeout 10m; | ||
proxy_send_timeout 10m; | ||
keepalive_timeout 10m; | ||
''; | ||
}; | ||
}; | ||
|
||
users.users = mkIf (cfg.user == "lms") { | ||
"${cfg.user}" = { | ||
isSystemUser = true; | ||
description = "LMS service user"; | ||
name = cfg.user; | ||
group = "lms"; | ||
home = cfg.stateDir; | ||
}; | ||
}; | ||
|
||
users.groups.lms = {}; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
{ stdenv | ||
, lib | ||
, fetchFromGitHub | ||
, writeText | ||
, cmake | ||
, pkg-config | ||
, gtest | ||
, boost | ||
, zlib | ||
, ffmpeg_4 | ||
, graphicsmagick | ||
, libconfig | ||
, taglib | ||
, wt, useMinimalWt ? false | ||
}: | ||
|
||
let | ||
ffmpeg = ffmpeg_4; | ||
|
||
# Wt with the minimum set of optional dependencies required for LMS | ||
wt-minimal = wt.override { | ||
# Docs not needed | ||
doxygen = null; | ||
# Graphics libraries not needed | ||
glew = null; libharu = null; pango = null; graphicsmagick = null; | ||
# LMS uses only sqlite | ||
firebird = null; libmysqlclient = null; postgresql = null; | ||
# Qt not used | ||
qt48Full = null; | ||
}; | ||
|
||
wt-lms = if useMinimalWt then wt-minimal else wt; | ||
in | ||
|
||
stdenv.mkDerivation rec { | ||
pname = "lms"; | ||
version = "3.27.0"; | ||
|
||
src = fetchFromGitHub { | ||
owner = "epoupon"; | ||
repo = pname; | ||
rev = "v${version}"; | ||
sha256 = "0rkky9c1yiqlgs9amrcnqsxdcwc7s7qghc1s1m0mbannsr1rb8rn"; | ||
}; | ||
|
||
nativeBuildInputs = [ cmake pkg-config gtest ]; | ||
buildInputs = [ | ||
boost.dev zlib.dev ffmpeg.dev graphicsmagick libconfig taglib wt-lms | ||
]; | ||
|
||
patches = [ | ||
(writeText "hardcode-dependency-paths.patch" '' | ||
diff --git a/src/libs/av/impl/Transcoder.cpp b/src/libs/av/impl/Transcoder.cpp | ||
index 7efa044..16a6e4c 100644 | ||
--- a/src/libs/av/impl/Transcoder.cpp | ||
+++ b/src/libs/av/impl/Transcoder.cpp | ||
@@ -38,7 +38,7 @@ static std::filesystem::path ffmpegPath; | ||
void | ||
Transcoder::init() | ||
{ | ||
- ffmpegPath = Service<IConfig>::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg"); | ||
+ ffmpegPath = Service<IConfig>::get()->getPath("ffmpeg-file", "${ffmpeg.bin}/bin/ffmpeg"); | ||
if (!std::filesystem::exists(ffmpegPath)) | ||
throw Exception {"File '" + ffmpegPath.string() + "' does not exist!"}; | ||
} | ||
Comment on lines
+55
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to https://github.com/epoupon/lms/blob/835e705e09176797ce9f910640ac091c80b5f717/conf/lms.conf#L8 we could do this in the module instead: services.lms.settings.ffmpeg-file = "${pkgs.ffmpeg_4.bin}/bin/ffmpeg"; Or is the issue that you want to be able to use this software without a configuration file? |
||
diff --git a/src/lms/main.cpp b/src/lms/main.cpp | ||
index 98c0cec..b60e26a 100644 | ||
--- a/src/lms/main.cpp | ||
+++ b/src/lms/main.cpp | ||
@@ -53,7 +53,7 @@ generateWtConfig(std::string execPath) | ||
const std::filesystem::path wtConfigPath {Service<IConfig>::get()->getPath("working-dir") / "wt_config.xml"}; | ||
const std::filesystem::path wtLogFilePath {Service<IConfig>::get()->getPath("log-file", "/var/log/lms.log")}; | ||
Comment on lines
+51
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please write that into a file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not possible, because I need a reference to the Or am I missing something..? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to add: the author of lms did not provide for a command line option to set those paths, so the usual solution of writing a wrapper shell script won't work here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be cleaner to write the diff into a file with placeholders for the parts you need to modify and then use sed or something to fix them up at configure time. I think that's what Sandro meant. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's possible, but seems way uglier than simply inlining the patch? |
||
const std::filesystem::path wtAccessLogFilePath {Service<IConfig>::get()->getPath("access-log-file", "/var/log/lms.access.log")}; | ||
- const std::filesystem::path wtResourcesPath {Service<IConfig>::get()->getPath("wt-resources", "/usr/share/Wt/resources")}; | ||
+ const std::filesystem::path wtResourcesPath {Service<IConfig>::get()->getPath("wt-resources", "${wt-lms}/share/Wt/resources")}; | ||
Comment on lines
+74
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is everyone opposed to: postPatch = ''
substituteInPlace src/lms/main.cpp \
--replace "/usr/share/Wt/resources" "${wt-lms}/share/Wt/resources" \
--replace "/usr/bin/ffmpeg" "${ffmpeg_4.bin}/bin/ffmpeg"
''; ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess there is a bit of a worry that it might accidentally match other lines than the ones in this patch? But then again those paths would never be correct on NixOS you have a fair point there. Then again less patching is better so if we can get away with only a config tweak I'd just leave it entirely unpatched. Seems like the config file has an entry for the |
||
const unsigned long configHttpServerThreadCount {Service<IConfig>::get()->getULong("http-server-thread-count", 0)}; | ||
|
||
args.push_back(execPath); | ||
'') | ||
]; | ||
|
||
meta = with lib; { | ||
description = "Lightweight / low memory music streaming server"; | ||
longDescription = '' | ||
LMS is a self-hosted music streaming software: access your music | ||
collection from anywhere using a web interface or the subsonic API! | ||
Written in C++, it has a low memory footprint and even comes with a | ||
recommendation engine. | ||
''; | ||
homepage = https://github.com/epoupon/lms; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since https://github.com/NixOS/rfcs/blob/master/rfcs/0045-deprecate-url-syntax.md these need to be quoted. |
||
downloadPage = "https://github.com/epoupon/lms"; | ||
maintainers = with maintainers; [ arthur ]; | ||
license = licenses.gpl3; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
platforms = platforms.all; | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really hacky and
? null
should be avoided as much as possible. Can we do this different?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what better way there is. 1) The wt derivation (in
development/libraries/wt/default.nix
) does not provide a nicer mechanism to remove components and 2) I'm don't think that the wt derivation should be expanded, given that wt simply does not build the optional components if it doesn't find the required dependencies, and that cutting out optional dependencies is likely only useful on a system that isn't served by a binary cache (e.g. armv7l, on which those dependencies are broken half the time). Having the caller pass in null seems like a robust and minimal way to include or drop those optional portions.Or, are you against providing the "useMinimalWt" option at all? I'm happy to remove that and keep it in my overlays; I included it because I thought others might find it useful.