factorio: rudimentary mod support for factorio's nixos module

+111 -14
+38 -10
nixos/modules/services/games/factorio.nix
··· 4 5 let 6 cfg = config.services.factorio; 7 name = "Factorio"; 8 stateDir = "/var/lib/factorio"; 9 configFile = pkgs.writeText "factorio.conf" '' 10 use-system-read-write-data-directories=true 11 [path] 12 - read-data=${pkgs.factorio-headless}/share/factorio/data 13 write-data=${stateDir} 14 ''; 15 in 16 { 17 options = { ··· 32 description = '' 33 The name of the savegame that will be used by the server. 34 35 - When not present in ${stateDir}/saves, it will be generated before starting the service. 36 ''; 37 }; 38 # TODO Add more individual settings as nixos-options? ··· 51 customizations. 52 ''; 53 }; 54 }; 55 }; 56 ··· 74 wantedBy = [ "multi-user.target" ]; 75 after = [ "network.target" ]; 76 77 - preStart = '' 78 - test -e ${stateDir}/saves/${cfg.saveName}.zip || \ 79 - ${pkgs.factorio-headless}/bin/factorio \ 80 - --config=${cfg.configFile} \ 81 - --create=${stateDir}/saves/${cfg.saveName}.zip 82 - ''; 83 84 serviceConfig = { 85 User = "factorio"; ··· 90 PrivateTmp = true; 91 UMask = "0007"; 92 ExecStart = toString [ 93 - "${pkgs.factorio-headless}/bin/factorio" 94 "--config=${cfg.configFile}" 95 "--port=${toString cfg.port}" 96 - "--start-server=${stateDir}/saves/${cfg.saveName}.zip" 97 ]; 98 }; 99 };
··· 4 5 let 6 cfg = config.services.factorio; 7 + factorio = pkgs.factorio-headless; 8 name = "Factorio"; 9 stateDir = "/var/lib/factorio"; 10 + mkSavePath = name: "${stateDir}/saves/${name}.zip"; 11 configFile = pkgs.writeText "factorio.conf" '' 12 use-system-read-write-data-directories=true 13 [path] 14 + read-data=${factorio}/share/factorio/data 15 write-data=${stateDir} 16 ''; 17 + modDir = pkgs.factorio-mkModDirDrv cfg.mods; 18 in 19 { 20 options = { ··· 35 description = '' 36 The name of the savegame that will be used by the server. 37 38 + When not present in ${stateDir}/saves, a new map with default 39 + settings will be generated before starting the service. 40 ''; 41 }; 42 # TODO Add more individual settings as nixos-options? ··· 55 customizations. 56 ''; 57 }; 58 + mods = mkOption { 59 + type = types.listOf types.package; 60 + default = []; 61 + description = '' 62 + Mods the server should install and activate. 63 + 64 + The derivations in this list must "build" the mod by simply copying 65 + the .zip, named correctly, into the output directory. Eventually, 66 + there will be a way to pull in the most up-to-date list of 67 + derivations via nixos-channel. Until then, this is for experts only. 68 + ''; 69 + }; 70 + autosave-interval = mkOption { 71 + type = types.nullOr types.int; 72 + default = null; 73 + example = 2; 74 + description = '' 75 + The time, in minutes, between autosaves. 76 + ''; 77 + }; 78 }; 79 }; 80 ··· 98 wantedBy = [ "multi-user.target" ]; 99 after = [ "network.target" ]; 100 101 + preStart = toString [ 102 + "test -e ${stateDir}/saves/${cfg.saveName}.zip" 103 + "||" 104 + "${factorio}/bin/factorio" 105 + "--config=${cfg.configFile}" 106 + "--create=${mkSavePath cfg.saveName}" 107 + (optionalString (cfg.mods != []) "--mod-directory=${modDir}") 108 + ]; 109 110 serviceConfig = { 111 User = "factorio"; ··· 116 PrivateTmp = true; 117 UMask = "0007"; 118 ExecStart = toString [ 119 + "${factorio}/bin/factorio" 120 "--config=${cfg.configFile}" 121 "--port=${toString cfg.port}" 122 + "--start-server=${mkSavePath cfg.saveName}" 123 + (optionalString (cfg.mods != []) "--mod-directory=${modDir}") 124 + (optionalString (cfg.autosave-interval != null) "--autosave-interval ${toString cfg.autosave-interval}") 125 ]; 126 }; 127 };
+22 -4
pkgs/games/factorio/default.nix
··· 1 { stdenv, callPackage, fetchurl, makeWrapper 2 , alsaLib, libX11, libXcursor, libXinerama, libXrandr, libXi, mesa_noglu 3 , releaseType 4 , username ? "" , password ? "" 5 }: 6 ··· 54 fi 55 ''; 56 57 base = { 58 name = "factorio-${releaseType}-${version}"; 59 60 src = fetch.${arch.inTar}.${releaseType}; 61 62 dontBuild = true; 63 64 - # TODO detangle headless/normal mode wrapping, libs, etc. test all urls 32/64/headless/gfx 65 installPhase = '' 66 mkdir -p $out/{bin,share/factorio} 67 cp -a data $out/share/factorio ··· 71 $out/bin/factorio 72 ''; 73 74 - preferLocalBuild = true; 75 - 76 meta = { 77 description = "A game in which you build and maintain factories"; 78 longDescription = '' ··· 112 wrapProgram $out/bin/factorio \ 113 --prefix LD_LIBRARY_PATH : /run/opengl-driver/lib:$libPath \ 114 --run "$out/share/factorio/update-config.sh" \ 115 - --add-flags "-c \$HOME/.factorio/config.cfg" 116 117 install -m0644 <(cat << EOF 118 ${configBaseCfg}
··· 1 { stdenv, callPackage, fetchurl, makeWrapper 2 , alsaLib, libX11, libXcursor, libXinerama, libXrandr, libXi, mesa_noglu 3 + , factorio-utils 4 , releaseType 5 + , mods ? [] 6 , username ? "" , password ? "" 7 }: 8 ··· 56 fi 57 ''; 58 59 + modDir = factorio-utils.mkModDirDrv mods; 60 + 61 base = { 62 name = "factorio-${releaseType}-${version}"; 63 64 src = fetch.${arch.inTar}.${releaseType}; 65 66 + preferLocalBuild = true; 67 dontBuild = true; 68 69 installPhase = '' 70 mkdir -p $out/{bin,share/factorio} 71 cp -a data $out/share/factorio ··· 75 $out/bin/factorio 76 ''; 77 78 meta = { 79 description = "A game in which you build and maintain factories"; 80 longDescription = '' ··· 114 wrapProgram $out/bin/factorio \ 115 --prefix LD_LIBRARY_PATH : /run/opengl-driver/lib:$libPath \ 116 --run "$out/share/factorio/update-config.sh" \ 117 + --add-flags "-c \$HOME/.factorio/config.cfg ${optionalString (mods != []) "--mod-directory=${modDir}"}" 118 + 119 + # TODO Currently, every time a mod is changed/added/removed using the 120 + # modlist, a new derivation will take up the entire footprint of the 121 + # client. The only way to avoid this is to remove the mods arg from the 122 + # package function. The modsDir derivation will have to be built 123 + # separately and have the user specify it in the .factorio config or 124 + # right along side it using a symlink into the store I think i will 125 + # just remove mods for the client derivation entirely. this is much 126 + # cleaner and more useful for headless mode. 127 + 128 + # TODO: trying to toggle off a mod will result in read-only-fs-error. 129 + # not much we can do about that except warn the user somewhere. In 130 + # fact, no exit will be clean, since this error will happen on close 131 + # regardless. just prints an ugly stacktrace but seems to be otherwise 132 + # harmless, unless maybe the user forgets and tries to use the mod 133 + # manager. 134 135 install -m0644 <(cat << EOF 136 ${configBaseCfg}
+49
pkgs/games/factorio/utils.nix
···
··· 1 + # This file provides a top-level function that will be used by both nixpkgs and nixos 2 + # to generate mod directories for use at runtime by factorio. 3 + { stdenv }: 4 + with stdenv.lib; 5 + { 6 + mkModDirDrv = mods: # a list of mod derivations 7 + let 8 + recursiveDeps = modDrv: [modDrv] ++ optionals (modDrv.deps == []) (map recursiveDeps modDrv.deps); 9 + modDrvs = unique (flatten (map recursiveDeps mods)); 10 + in 11 + stdenv.mkDerivation { 12 + name = "factorio-mod-directory"; 13 + 14 + preferLocalBuild = true; 15 + buildCommand = '' 16 + mkdir -p $out 17 + for modDrv in ${toString modDrvs}; do 18 + # NB: there will only ever be a single zip file in each mod derivation's output dir 19 + ln -s $modDrv/*.zip $out 20 + done 21 + ''; 22 + }; 23 + 24 + modDrv = { allRecommendedMods, allOptionalMods }: 25 + { src 26 + , name ? null 27 + , deps ? [] 28 + , optionalDeps ? [] 29 + , recommendedDeps ? [] 30 + }: stdenv.mkDerivation { 31 + 32 + inherit src; 33 + 34 + # Use the name of the zip, but endstrip ".zip" and possibly the querystring that gets left in by fetchurl 35 + name = replaceStrings ["_"] ["-"] (if name != null then name else removeSuffix ".zip" (head (splitString "?" src.name))); 36 + 37 + deps = deps ++ optionals allOptionalMods optionalDeps 38 + ++ optionals allRecommendedMods recommendedDeps; 39 + 40 + preferLocalBuild = true; 41 + buildCommand = '' 42 + mkdir -p $out 43 + srcBase=$(basename $src) 44 + srcBase=''${srcBase#*-} # strip nix hash 45 + srcBase=''${srcBase%\?*} # strip querystring leftover from fetchurl 46 + cp $src $out/$srcBase 47 + ''; 48 + }; 49 + }
+2
pkgs/top-level/all-packages.nix
··· 15594 15595 factorio-headless = callPackage ../games/factorio { releaseType = "headless"; }; 15596 15597 fairymax = callPackage ../games/fairymax {}; 15598 15599 fish-fillets-ng = callPackage ../games/fish-fillets-ng {};
··· 15594 15595 factorio-headless = callPackage ../games/factorio { releaseType = "headless"; }; 15596 15597 + factorio-utils = callPackage ../games/factorio/utils.nix { }; 15598 + 15599 fairymax = callPackage ../games/fairymax {}; 15600 15601 fish-fillets-ng = callPackage ../games/fish-fillets-ng {};