···4455let
66 cfg = config.services.factorio;
77+ factorio = pkgs.factorio-headless;
78 name = "Factorio";
89 stateDir = "/var/lib/factorio";
1010+ mkSavePath = name: "${stateDir}/saves/${name}.zip";
911 configFile = pkgs.writeText "factorio.conf" ''
1012 use-system-read-write-data-directories=true
1113 [path]
1212- read-data=${pkgs.factorio-headless}/share/factorio/data
1414+ read-data=${factorio}/share/factorio/data
1315 write-data=${stateDir}
1416 '';
1717+ modDir = pkgs.factorio-mkModDirDrv cfg.mods;
1518in
1619{
1720 options = {
···3235 description = ''
3336 The name of the savegame that will be used by the server.
34373535- When not present in ${stateDir}/saves, it will be generated before starting the service.
3838+ When not present in ${stateDir}/saves, a new map with default
3939+ settings will be generated before starting the service.
3640 '';
3741 };
3842 # TODO Add more individual settings as nixos-options?
···5155 customizations.
5256 '';
5357 };
5858+ mods = mkOption {
5959+ type = types.listOf types.package;
6060+ default = [];
6161+ description = ''
6262+ Mods the server should install and activate.
6363+6464+ The derivations in this list must "build" the mod by simply copying
6565+ the .zip, named correctly, into the output directory. Eventually,
6666+ there will be a way to pull in the most up-to-date list of
6767+ derivations via nixos-channel. Until then, this is for experts only.
6868+ '';
6969+ };
7070+ autosave-interval = mkOption {
7171+ type = types.nullOr types.int;
7272+ default = null;
7373+ example = 2;
7474+ description = ''
7575+ The time, in minutes, between autosaves.
7676+ '';
7777+ };
5478 };
5579 };
5680···7498 wantedBy = [ "multi-user.target" ];
7599 after = [ "network.target" ];
761007777- preStart = ''
7878- test -e ${stateDir}/saves/${cfg.saveName}.zip || \
7979- ${pkgs.factorio-headless}/bin/factorio \
8080- --config=${cfg.configFile} \
8181- --create=${stateDir}/saves/${cfg.saveName}.zip
8282- '';
101101+ preStart = toString [
102102+ "test -e ${stateDir}/saves/${cfg.saveName}.zip"
103103+ "||"
104104+ "${factorio}/bin/factorio"
105105+ "--config=${cfg.configFile}"
106106+ "--create=${mkSavePath cfg.saveName}"
107107+ (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
108108+ ];
8310984110 serviceConfig = {
85111 User = "factorio";
···90116 PrivateTmp = true;
91117 UMask = "0007";
92118 ExecStart = toString [
9393- "${pkgs.factorio-headless}/bin/factorio"
119119+ "${factorio}/bin/factorio"
94120 "--config=${cfg.configFile}"
95121 "--port=${toString cfg.port}"
9696- "--start-server=${stateDir}/saves/${cfg.saveName}.zip"
122122+ "--start-server=${mkSavePath cfg.saveName}"
123123+ (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
124124+ (optionalString (cfg.autosave-interval != null) "--autosave-interval ${toString cfg.autosave-interval}")
97125 ];
98126 };
99127 };
+22-4
pkgs/games/factorio/default.nix
···11{ stdenv, callPackage, fetchurl, makeWrapper
22, alsaLib, libX11, libXcursor, libXinerama, libXrandr, libXi, mesa_noglu
33+, factorio-utils
34, releaseType
55+, mods ? []
46, username ? "" , password ? ""
57}:
68···5456 fi
5557 '';
56585959+ modDir = factorio-utils.mkModDirDrv mods;
6060+5761 base = {
5862 name = "factorio-${releaseType}-${version}";
59636064 src = fetch.${arch.inTar}.${releaseType};
61656666+ preferLocalBuild = true;
6267 dontBuild = true;
63686464- # TODO detangle headless/normal mode wrapping, libs, etc. test all urls 32/64/headless/gfx
6569 installPhase = ''
6670 mkdir -p $out/{bin,share/factorio}
6771 cp -a data $out/share/factorio
···7175 $out/bin/factorio
7276 '';
73777474- preferLocalBuild = true;
7575-7678 meta = {
7779 description = "A game in which you build and maintain factories";
7880 longDescription = ''
···112114 wrapProgram $out/bin/factorio \
113115 --prefix LD_LIBRARY_PATH : /run/opengl-driver/lib:$libPath \
114116 --run "$out/share/factorio/update-config.sh" \
115115- --add-flags "-c \$HOME/.factorio/config.cfg"
117117+ --add-flags "-c \$HOME/.factorio/config.cfg ${optionalString (mods != []) "--mod-directory=${modDir}"}"
118118+119119+ # TODO Currently, every time a mod is changed/added/removed using the
120120+ # modlist, a new derivation will take up the entire footprint of the
121121+ # client. The only way to avoid this is to remove the mods arg from the
122122+ # package function. The modsDir derivation will have to be built
123123+ # separately and have the user specify it in the .factorio config or
124124+ # right along side it using a symlink into the store I think i will
125125+ # just remove mods for the client derivation entirely. this is much
126126+ # cleaner and more useful for headless mode.
127127+128128+ # TODO: trying to toggle off a mod will result in read-only-fs-error.
129129+ # not much we can do about that except warn the user somewhere. In
130130+ # fact, no exit will be clean, since this error will happen on close
131131+ # regardless. just prints an ugly stacktrace but seems to be otherwise
132132+ # harmless, unless maybe the user forgets and tries to use the mod
133133+ # manager.
116134117135 install -m0644 <(cat << EOF
118136 ${configBaseCfg}
+49
pkgs/games/factorio/utils.nix
···11+# This file provides a top-level function that will be used by both nixpkgs and nixos
22+# to generate mod directories for use at runtime by factorio.
33+{ stdenv }:
44+with stdenv.lib;
55+{
66+ mkModDirDrv = mods: # a list of mod derivations
77+ let
88+ recursiveDeps = modDrv: [modDrv] ++ optionals (modDrv.deps == []) (map recursiveDeps modDrv.deps);
99+ modDrvs = unique (flatten (map recursiveDeps mods));
1010+ in
1111+ stdenv.mkDerivation {
1212+ name = "factorio-mod-directory";
1313+1414+ preferLocalBuild = true;
1515+ buildCommand = ''
1616+ mkdir -p $out
1717+ for modDrv in ${toString modDrvs}; do
1818+ # NB: there will only ever be a single zip file in each mod derivation's output dir
1919+ ln -s $modDrv/*.zip $out
2020+ done
2121+ '';
2222+ };
2323+2424+ modDrv = { allRecommendedMods, allOptionalMods }:
2525+ { src
2626+ , name ? null
2727+ , deps ? []
2828+ , optionalDeps ? []
2929+ , recommendedDeps ? []
3030+ }: stdenv.mkDerivation {
3131+3232+ inherit src;
3333+3434+ # Use the name of the zip, but endstrip ".zip" and possibly the querystring that gets left in by fetchurl
3535+ name = replaceStrings ["_"] ["-"] (if name != null then name else removeSuffix ".zip" (head (splitString "?" src.name)));
3636+3737+ deps = deps ++ optionals allOptionalMods optionalDeps
3838+ ++ optionals allRecommendedMods recommendedDeps;
3939+4040+ preferLocalBuild = true;
4141+ buildCommand = ''
4242+ mkdir -p $out
4343+ srcBase=$(basename $src)
4444+ srcBase=''${srcBase#*-} # strip nix hash
4545+ srcBase=''${srcBase%\?*} # strip querystring leftover from fetchurl
4646+ cp $src $out/$srcBase
4747+ '';
4848+ };
4949+}