grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / games / archisteamfarm.nix
blob630329175968d43c023287663f203b43d5fb181e
1 { config, lib, pkgs, ... }:
3 let
4   cfg = config.services.archisteamfarm;
6   format = pkgs.formats.json { };
8   configFile = format.generate "ASF.json" (cfg.settings // {
9     # we disable it because ASF cannot update itself anyways
10     # and nixos takes care of restarting the service
11     # is in theory not needed as this is already the default for default builds
12     UpdateChannel = 0;
13     Headless = true;
14   } // lib.optionalAttrs (cfg.ipcPasswordFile != null) {
15     IPCPassword = "#ipcPassword#";
16   });
18   ipc-config = format.generate "IPC.config" cfg.ipcSettings;
20   mkBot = n: c:
21     format.generate "${n}.json" (c.settings // {
22       SteamLogin = if c.username == "" then n else c.username;
23       Enabled = c.enabled;
24     } // lib.optionalAttrs (c.passwordFile != null) {
25       SteamPassword = c.passwordFile;
26       # sets the password format to file (https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Security#file)
27       PasswordFormat = 4;
28     });
31   options.services.archisteamfarm = {
32     enable = lib.mkOption {
33       type = lib.types.bool;
34       description = ''
35         If enabled, starts the ArchisSteamFarm service.
36         For configuring the SteamGuard token you will need to use the web-ui, which is enabled by default over on 127.0.0.1:1242.
37         You cannot configure ASF in any way outside of nix, since all the config files get wiped on restart and replaced with the programnatically set ones by nix.
38       '';
39       default = false;
40     };
42     web-ui = lib.mkOption {
43       type = lib.types.submodule {
44         options = {
45           enable = lib.mkEnableOption "" // {
46             description = "Whether to start the web-ui. This is the preferred way of configuring things such as the steam guard token.";
47           };
49           package = lib.mkPackageOption pkgs [ "ArchiSteamFarm" "ui" ] {
50             extraDescription = ''
51               ::: {.note}
52               Contents must be in lib/dist
53               :::
54             '';
55           };
56         };
57       };
58       default = {
59         enable = true;
60       };
61       example = {
62         enable = false;
63       };
64       description = "The Web-UI hosted on 127.0.0.1:1242.";
65     };
67     package = lib.mkPackageOption pkgs "ArchiSteamFarm" {
68       extraDescription = ''
69         ::: {.warning}
70         Should always be the latest version, for security reasons,
71         since this module uses very new features and to not get out of sync with the Steam API.
72         :::
73       '';
74     };
76     dataDir = lib.mkOption {
77       type = lib.types.path;
78       default = "/var/lib/archisteamfarm";
79       description = ''
80         The ASF home directory used to store all data.
81         If left as the default value this directory will automatically be created before the ASF server starts, otherwise the sysadmin is responsible for ensuring the directory exists with appropriate ownership and permissions.'';
82     };
84     settings = lib.mkOption {
85       type = format.type;
86       description = ''
87         The ASF.json file, all the options are documented [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#global-config).
88         Do note that `AutoRestart`  and `UpdateChannel` is always to `false` respectively `0` because NixOS takes care of updating everything.
89         `Headless` is also always set to `true` because there is no way to provide inputs via a systemd service.
90         You should try to keep ASF up to date since upstream does not provide support for anything but the latest version and you're exposing yourself to all kinds of issues - as is outlined [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#updateperiod).
91       '';
92       example = {
93         Statistics = false;
94       };
95       default = { };
96     };
98     ipcPasswordFile = lib.mkOption {
99       type = with lib.types; nullOr path;
100       default = null;
101       description = "Path to a file containing the password. The file must be readable by the `archisteamfarm` user/group.";
102     };
104     ipcSettings = lib.mkOption {
105       type = format.type;
106       description = ''
107         Settings to write to IPC.config.
108         All options can be found [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/IPC#custom-configuration).
109       '';
110       example = {
111         Kestrel = {
112           Endpoints = {
113             HTTP = {
114               Url = "http://*:1242";
115             };
116           };
117         };
118       };
119       default = { };
120     };
122     bots = lib.mkOption {
123       type = lib.types.attrsOf (lib.types.submodule {
124         options = {
125           username = lib.mkOption {
126             type = lib.types.str;
127             description = "Name of the user to log in. Default is attribute name.";
128             default = "";
129           };
130           passwordFile = lib.mkOption {
131             type = with lib.types; nullOr path;
132             default = null;
133             description = ''
134               Path to a file containing the password. The file must be readable by the `archisteamfarm` user/group.
135               Omit or set to null to provide the password a different way, such as through the web-ui.
136             '';
137           };
138           enabled = lib.mkOption {
139             type = lib.types.bool;
140             default = true;
141             description = "Whether to enable the bot on startup.";
142           };
143           settings = lib.mkOption {
144             type = lib.types.attrs;
145             description = ''
146               Additional settings that are documented [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#bot-config).
147             '';
148             default = { };
149           };
150         };
151       });
152       description = ''
153         Bots name and configuration.
154       '';
155       example = {
156         exampleBot = {
157           username = "alice";
158           passwordFile = "/var/lib/archisteamfarm/secrets/password";
159           settings = { SteamParentalCode = "1234"; };
160         };
161       };
162       default = { };
163     };
164   };
166   config = lib.mkIf cfg.enable {
167     # TODO: drop with 24.11
168     services.archisteamfarm.dataDir = lib.mkIf (lib.versionAtLeast config.system.stateVersion "24.05") (lib.mkDefault "/var/lib/asf");
170     users = {
171       users.archisteamfarm = {
172         home = cfg.dataDir;
173         isSystemUser = true;
174         group = "archisteamfarm";
175         description = "Archis-Steam-Farm service user";
176       };
177       groups.archisteamfarm = { };
178     };
180     systemd.services = {
181       archisteamfarm = {
182         description = "Archis-Steam-Farm Service";
183         after = [ "network.target" ];
184         wantedBy = [ "multi-user.target" ];
186         serviceConfig = lib.mkMerge [
187           (lib.mkIf (lib.hasPrefix "/var/lib/" cfg.dataDir) {
188             StateDirectory = lib.last (lib.splitString "/" cfg.dataDir);
189             StateDirectoryMode = "700";
190           })
191           {
192             User = "archisteamfarm";
193             Group = "archisteamfarm";
194             WorkingDirectory = cfg.dataDir;
195             Type = "simple";
196             ExecStart = "${lib.getExe cfg.package} --no-restart --service --system-required --path ${cfg.dataDir}";
197             Restart = "always";
199             # copied from the default systemd service at
200             # https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/ArchiSteamFarm/overlay/variant-base/linux/ArchiSteamFarm%40.service
201             CapabilityBoundingSet = "";
202             DevicePolicy = "closed";
203             LockPersonality = true;
204             NoNewPrivileges = true;
205             PrivateDevices = true;
206             PrivateIPC = true;
207             PrivateMounts = true;
208             PrivateTmp = true; # instead of rw /tmp
209             PrivateUsers = true;
210             ProcSubset = "pid";
211             ProtectClock = true;
212             ProtectControlGroups = true;
213             ProtectHome = true;
214             ProtectHostname = true;
215             ProtectKernelLogs = true;
216             ProtectKernelModules = true;
217             ProtectKernelTunables = true;
218             ProtectProc = "invisible";
219             ProtectSystem = "strict";
220             RemoveIPC = true;
221             RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK AF_UNIX";
222             RestrictNamespaces = true;
223             RestrictRealtime = true;
224             RestrictSUIDSGID = true;
225             SecureBits = "noroot-locked";
226             SystemCallArchitectures = "native";
227             SystemCallFilter = [ "@system-service" "~@privileged" ];
228             UMask = "0077";
229           }
230         ];
232         preStart =
233           let
234             createBotsScript = pkgs.runCommandLocal "ASF-bots" { } ''
235               mkdir -p $out
236               # clean potential removed bots
237               rm -rf $out/*.json
238               for i in ${lib.concatStringsSep " " (map (x: "${lib.getName x},${x}") (lib.mapAttrsToList mkBot cfg.bots))}; do IFS=",";
239                 set -- $i
240                 ln -fs $2 $out/$1
241               done
242             '';
243             replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
244           in
245           ''
246             mkdir -p config
248             cp --no-preserve=mode ${configFile} config/ASF.json
250             ${lib.optionalString (cfg.ipcPasswordFile != null) ''
251               ${replaceSecretBin} '#ipcPassword#' '${cfg.ipcPasswordFile}' config/ASF.json
252             ''}
254             ${lib.optionalString (cfg.ipcSettings != {}) ''
255               ln -fs ${ipc-config} config/IPC.config
256             ''}
258             ${lib.optionalString (cfg.bots != {}) ''
259               ln -fs ${createBotsScript}/* config/
260             ''}
262             rm -f www
263             ${lib.optionalString cfg.web-ui.enable ''
264               ln -s ${cfg.web-ui.package}/ www
265             ''}
266           '';
267       };
268     };
269   };
271   meta = {
272     buildDocsInSandbox = false;
273     maintainers = with lib.maintainers; [ SuperSandro2000 ];
274   };