grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / web-apps / your_spotify.nix
blob3eb2ffef4f9338c8f4e8fedd9906bdf5c2aaf636
2   pkgs,
3   config,
4   lib,
5   ...
6 }: let
7   inherit
8     (lib)
9     boolToString
10     concatMapAttrs
11     concatStrings
12     isBool
13     mapAttrsToList
14     mkEnableOption
15     mkIf
16     mkOption
17     mkPackageOption
18     optionalAttrs
19     types
20     mkDefault
21     ;
22   cfg = config.services.your_spotify;
24   configEnv = concatMapAttrs (name: value:
25     optionalAttrs (value != null) {
26       ${name} =
27         if isBool value
28         then boolToString value
29         else toString value;
30     })
31   cfg.settings;
33   configFile = pkgs.writeText "your_spotify.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
34 in {
35   options.services.your_spotify = let
36     inherit (types) nullOr port str path package;
37   in {
38     enable = mkEnableOption "your_spotify";
40     enableLocalDB = mkEnableOption "a local mongodb instance";
41     nginxVirtualHost = mkOption {
42       type = nullOr str;
43       default = null;
44       description = ''
45         If set creates an nginx virtual host for the client.
46         In most cases this should be the CLIENT_ENDPOINT without
47         protocol prefix.
48       '';
49     };
51     package = mkPackageOption pkgs "your_spotify" {};
53     clientPackage = mkOption {
54       type = package;
55       description = "Client package to use.";
56     };
58     spotifySecretFile = mkOption {
59       type = path;
60       description = ''
61         A file containing the secret key of your Spotify application.
62         Refer to: [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application).
63       '';
64     };
66     settings = mkOption {
67       description = ''
68         Your Spotify Configuration. Refer to [Your Spotify](https://github.com/Yooooomi/your_spotify) for definitions and values.
69       '';
70       example = lib.literalExpression ''
71         {
72           CLIENT_ENDPOINT = "https://example.com";
73           API_ENDPOINT = "https://api.example.com";
74           SPOTIFY_PUBLIC = "spotify_client_id";
75         }
76       '';
77       type = types.submodule {
78         freeformType = types.attrsOf types.str;
79         options = {
80           CLIENT_ENDPOINT = mkOption {
81             type = str;
82             description = ''
83               The endpoint of your web application.
84               Has to include a protocol Prefix (e.g. `http://`)
85             '';
86             example = "https://your_spotify.example.org";
87           };
88           API_ENDPOINT = mkOption {
89             type = str;
90             description = ''
91               The endpoint of your server
92               This api has to be reachable from the device you use the website from not from the server.
93               This means that for example you may need two nginx virtual hosts if you want to expose this on the
94               internet.
95               Has to include a protocol Prefix (e.g. `http://`)
96             '';
97             example = "https://localhost:3000";
98           };
99           SPOTIFY_PUBLIC = mkOption {
100             type = str;
101             description = ''
102               The public client ID of your Spotify application.
103               Refer to: [Creating the Spotify Application](https://github.com/Yooooomi/your_spotify#creating-the-spotify-application)
104             '';
105           };
106           MONGO_ENDPOINT = mkOption {
107             type = str;
108             description = ''The endpoint of the Mongo database.'';
109             default = "mongodb://localhost:27017/your_spotify";
110           };
111           PORT = mkOption {
112             type = port;
113             description = "The port of the api server";
114             default = 3000;
115           };
116         };
117       };
118     };
119   };
121   config = mkIf cfg.enable {
122     services.your_spotify.clientPackage = mkDefault (cfg.package.client.override {apiEndpoint = cfg.settings.API_ENDPOINT;});
123     systemd.services.your_spotify = {
124       after = ["network.target"];
125       script = ''
126         export SPOTIFY_SECRET=$(< "$CREDENTIALS_DIRECTORY/SPOTIFY_SECRET")
127         ${lib.getExe' cfg.package "your_spotify_migrate"}
128         exec ${lib.getExe cfg.package}
129       '';
130       serviceConfig = {
131         User = "your_spotify";
132         Group = "your_spotify";
133         DynamicUser = true;
134         EnvironmentFile = [configFile];
135         StateDirectory = "your_spotify";
136         LimitNOFILE = "1048576";
137         PrivateTmp = true;
138         PrivateDevices = true;
139         StateDirectoryMode = "0700";
140         Restart = "always";
142         LoadCredential = ["SPOTIFY_SECRET:${cfg.spotifySecretFile}"];
144         # Hardening
145         CapabilityBoundingSet = "";
146         LockPersonality = true;
147         #MemoryDenyWriteExecute = true; # Leads to coredump because V8 does JIT
148         PrivateUsers = true;
149         ProtectClock = true;
150         ProtectControlGroups = true;
151         ProtectHome = true;
152         ProtectHostname = true;
153         ProtectKernelLogs = true;
154         ProtectKernelModules = true;
155         ProtectKernelTunables = true;
156         ProtectProc = "invisible";
157         ProcSubset = "pid";
158         ProtectSystem = "strict";
159         RestrictAddressFamilies = [
160           "AF_INET"
161           "AF_INET6"
162           "AF_NETLINK"
163         ];
164         RestrictNamespaces = true;
165         RestrictRealtime = true;
166         SystemCallArchitectures = "native";
167         SystemCallFilter = [
168           "@system-service"
169           "@pkey"
170         ];
171         UMask = "0077";
172       };
173       wantedBy = ["multi-user.target"];
174     };
175     services.nginx = mkIf (cfg.nginxVirtualHost != null) {
176       enable = true;
177       virtualHosts.${cfg.nginxVirtualHost} = {
178         root = cfg.clientPackage;
179         locations."/".extraConfig = ''
180           add_header Content-Security-Policy "frame-ancestors 'none';" ;
181           add_header X-Content-Type-Options "nosniff" ;
182           try_files = $uri $uri/ /index.html ;
183         '';
184       };
185     };
186     services.mongodb = mkIf cfg.enableLocalDB {
187       enable = true;
188     };
189   };
190   meta.maintainers = with lib.maintainers; [patrickdag];