diff --git a/nixos/doc/manual/release-notes/rl-2611.section.md b/nixos/doc/manual/release-notes/rl-2611.section.md index 1539b800fbcd..0c67d5a7b76e 100644 --- a/nixos/doc/manual/release-notes/rl-2611.section.md +++ b/nixos/doc/manual/release-notes/rl-2611.section.md @@ -24,6 +24,8 @@ - [Nezha](https://github.com/nezhahq/nezha), a self-hosted, lightweight server and website monitoring and O&M tool. Available as [services.nezha](#opt-services.nezha.enable). +- [feishin](https://github.com/jeffvli/feishin), a modern self-hosted music player. Available as [services.feishin](#opt-services.feishin.enable). + - [FlapAlerted](https://github.com/Kioubit/FlapAlerted), detects BGP flapping events and provides statistics based on BGP update messages. Available as [services.flap-alerted](#opt-services.flap-alerted.enable). ## Backward Incompatibilities {#sec-release-26.11-incompatibilities} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 05183e91e7b5..cee7c9535e9e 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1651,6 +1651,7 @@ ./services/web-apps/engelsystem.nix ./services/web-apps/ente.nix ./services/web-apps/fediwall.nix + ./services/web-apps/feishin.nix ./services/web-apps/fider.nix ./services/web-apps/filebrowser.nix ./services/web-apps/firefly-iii-data-importer.nix diff --git a/nixos/modules/services/web-apps/feishin.nix b/nixos/modules/services/web-apps/feishin.nix new file mode 100644 index 000000000000..5ddbd9c8ba87 --- /dev/null +++ b/nixos/modules/services/web-apps/feishin.nix @@ -0,0 +1,156 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.feishin; +in +{ + options.services.feishin = { + enable = lib.mkEnableOption "the web version of Feishin"; + + package = lib.mkPackageOption pkgs "feishin-web" { }; + + domain = lib.mkOption { + description = "Domain to use for the virtualhost."; + type = lib.types.str; + }; + + pathbase = lib.mkOption { + description = "URL path base to use for the virtualhost."; + type = lib.types.str; + default = ""; + example = "/music"; + }; + + settings = lib.mkOption { + description = '' + Configuration for the web version of Feishin, specified as key-value pairs. + + Refer to , and + + for the template. + ''; + type = lib.types.attrsOf lib.types.str; + default = { + ANALYTICS_DISABLED = "true"; + }; + example = { + ANALYTICS_DISABLED = "true"; + SERVER_NAME = "My Server"; + SERVER_LOCK = "true"; + }; + }; + + nginx = { + enable = lib.mkEnableOption "a virtualhost to serve Feishin through nginx"; + + virtualHost = lib.mkOption { + description = "Extra configuration for the nginx virtualhost for Feishin."; + type = lib.types.submodule (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }); + default = { }; + example = lib.literalExpression '' + { + serverAliases = [ "feishin.''${config.networking.domain}" ]; + } + ''; + }; + }; + + caddy = { + enable = lib.mkEnableOption "a virtualhost to serve Feishin through Caddy"; + + virtualHost = lib.mkOption { + description = "Extra configuration for the Caddy virtualhost for Feishin."; + type = lib.types.submodule ( + import ../web-servers/caddy/vhost-options.nix { cfg = config.services.caddy; } + ); + default = { }; + example = lib.literalExpression '' + { + serverAliases = [ "feishin.''${config.networking.domain}" ]; + } + ''; + }; + }; + }; + + config = + let + resolvedSettings = + cfg.settings // lib.optionalAttrs (cfg.pathbase != "") { PUBLIC_PATH = cfg.pathbase; }; + settingsJs = '' + "use strict"; + ${lib.concatMapAttrsStringSep "\n" (name: value: ''window.${name} = "${value}";'') resolvedSettings} + ''; + settingsJsDir = pkgs.writeTextDir "settings.js" settingsJs; + in + lib.mkIf cfg.enable { + services.nginx = lib.mkIf cfg.nginx.enable { + enable = lib.mkDefault true; + virtualHosts."${cfg.domain}" = lib.mkMerge [ + cfg.nginx.virtualHost + { + root = lib.mkForce "${cfg.package}"; + extraConfig = '' + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + ''; + locations."${cfg.pathbase}/" = { + tryFiles = "$uri $uri/ /index.html =404"; + }; + locations."=${cfg.pathbase}/settings.js" = { + alias = settingsJsDir + "/settings.js"; + extraConfig = '' + add_header Cache-Control "no-store"; + ''; + }; + } + ]; + }; + + services.caddy = lib.mkIf cfg.caddy.enable { + enable = lib.mkDefault true; + virtualHosts."${cfg.domain}" = lib.mkMerge [ + cfg.caddy.virtualHost + { + hostName = lib.mkForce cfg.domain; + extraConfig = + let + baseConfig = '' + encode + handle /settings.js { + header Cache-Control no-store + root ${settingsJsDir} + file_server + } + handle { + root ${cfg.package} + try_files {path} /index.html + file_server + } + ''; + in + if (cfg.pathbase == "") then + baseConfig + else + '' + handle_path ${cfg.pathbase}/* { + ${baseConfig} + } + ''; + } + ]; + }; + }; + + meta.maintainers = with lib.maintainers; [ + rharish + BatteredBunny + ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 37e0a09e81fc..0408a5cdcf34 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -560,6 +560,7 @@ in fastnetmon-advanced = runTest ./fastnetmon-advanced.nix; fcitx5 = runTest ./fcitx5; fedimintd = runTest ./fedimintd.nix; + feishin = handleTest ./feishin { }; ferm = runTest ./ferm.nix; ferretdb = import ./ferretdb.nix { inherit pkgs runTest; }; fider = runTest ./fider.nix; diff --git a/nixos/tests/feishin/caddy.nix b/nixos/tests/feishin/caddy.nix new file mode 100644 index 000000000000..1329c5fc3326 --- /dev/null +++ b/nixos/tests/feishin/caddy.nix @@ -0,0 +1,36 @@ +import ../make-test-python.nix ( + { lib, ... }: + + { + name = "feishin-caddy"; + meta.maintainers = with lib.maintainers; [ + rharish + BatteredBunny + ]; + + nodes.machine = + { pkgs, ... }: + { + services.feishin = { + enable = true; + domain = "localhost:80"; + pathbase = "/feishin"; + settings = { + ANALYTICS_DISABLED = "true"; + }; + caddy.enable = true; + caddy.virtualHost.extraConfig = "tls internal"; + }; + # disable letsencrypt cert fetching + services.caddy.globalConfig = "auto_https disable_certs"; + }; + + testScript = '' + machine.wait_for_unit("caddy.service") + machine.wait_for_open_port(80) + machine.succeed("curl -vvv --fail --show-error --silent --location --insecure http://localhost/feishin/") + assert "Feishin" in machine.succeed("curl --fail --show-error --silent --location --insecure http://localhost/feishin/") + assert "window.ANALYTICS_DISABLED = \"true\";" in machine.succeed("curl --fail --show-error --silent --location --insecure http://localhost/feishin/settings.js") + ''; + } +) diff --git a/nixos/tests/feishin/default.nix b/nixos/tests/feishin/default.nix new file mode 100644 index 000000000000..fee30e520c7f --- /dev/null +++ b/nixos/tests/feishin/default.nix @@ -0,0 +1,6 @@ +{ system, pkgs, ... }: + +{ + caddy = import ./caddy.nix { inherit system pkgs; }; + nginx = import ./nginx.nix { inherit system pkgs; }; +} diff --git a/nixos/tests/feishin/nginx.nix b/nixos/tests/feishin/nginx.nix new file mode 100644 index 000000000000..cfeeca40b04f --- /dev/null +++ b/nixos/tests/feishin/nginx.nix @@ -0,0 +1,33 @@ +import ../make-test-python.nix ( + { lib, ... }: + + { + name = "feishin-nginx"; + meta.maintainers = with lib.maintainers; [ + rharish + BatteredBunny + ]; + + nodes.machine = + { pkgs, ... }: + { + services.feishin = { + enable = true; + domain = "localhost"; + pathbase = "/feishin"; + settings = { + ANALYTICS_DISABLED = "true"; + }; + nginx.enable = true; + }; + }; + + testScript = '' + machine.wait_for_unit("nginx.service") + machine.wait_for_open_port(80) + machine.succeed("curl -vvv --fail --show-error --silent --location --insecure http://localhost/feishin/") + assert "Feishin" in machine.succeed("curl --fail --show-error --silent --location --insecure http://localhost/feishin/") + assert "window.ANALYTICS_DISABLED = \"true\";" in machine.succeed("curl --fail --show-error --silent --location --insecure http://localhost/feishin/settings.js") + ''; + } +)