# Workadventure NixOS module. Used to deploy fediventure-compatible instances. { config, lib, pkgs, ... }: with lib; let cfg = config.services.workadventure; workadventureOverlay = let src = pkgs.fetchgit { url = "https://gitlab.infra4future.de/stuebinm/workadventure-nix"; rev = "7ec4c291d8ec93bc205af5c238dfe6a5176f4da4"; sha256 = "1372iwwbw7ji3v1wx3ncx9xiwnmjhahb8ivxcg8ykkci80hj0ci5"; }; in import "${src}/overlay.nix"; urls = instanceConfig: if instanceConfig.nginx.domain != null then { api = instanceConfig.nginx.domain + instanceConfig.frontend.urls.api; uploader = instanceConfig.nginx.domain + instanceConfig.frontend.urls.uploader; admin = instanceConfig.nginx.domain + instanceConfig.frontend.urls.admin; maps = instanceConfig.nginx.domain + instanceConfig.frontend.urls.maps; } else instanceConfig.urls; servicesBack = mapAttrs' (instanceName: instanceConfig: { name = "wa-back-${instanceName}"; value = { description = "WorkAdventure backend ${instanceName}"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; # Hack to get node-grpc-precompiled to work on NixOS by adding getconf to # $PATH. # # It uses node-pre-gyp which attempts to select the right native module # via npmjs.com/package/detect-libc, which says 'yep, it's glibc' as long # as `getconf GNU_LIBC_VERSION` returns something sensible. This happens # during the build process (as stdenv.mkDerivation has enough of a glibc # dev env to make it work) but doesn't happen on production deployments # in which the environment is much more limited. This is regardless of # actual glibc ABI presence wrt. to /nix/store vs. /usr/lib64 paths. # # This should be fixed in workadventure-nix. path = [ pkgs.getconf ]; environment = { HTTP_PORT = toString instanceConfig.backend.httpPort; GRPC_PORT = toString instanceConfig.backend.grpcPort; #ADMIN_API_TOKEN = "lalala"; #ADMIN_API_URL = toString (urls instanceConfig).admin; #ALLOW_ARTILLERY = "true"; }; serviceConfig = { User = "workadventure-backend"; Group = "workadventure-backend"; DynamicUser = true; # Note: this implies a lot of other security features. ExecStart = "${instanceConfig.backend.package}/bin/workadventureback"; Restart = "always"; RestartSec = "10s"; }; }; }) cfg.instances; servicesPusher = mapAttrs' (instanceName: instanceConfig: { name = "wa-pusher-${instanceName}"; value = { description = "WorkAdventure pusher ${instanceName}"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; path = [ pkgs.getconf ]; environment = { PUSHER_HTTP_PORT = toString instanceConfig.pusher.port; API_URL = "localhost:${toString instanceConfig.backend.grpcPort}"; #ADMIN_API_URL = toString (urls instanceConfig).admin; #ADMIN_API_TOKEN = "lalala"; }; serviceConfig = { User = "workadventure-pusher"; Group = "workadventure-pusher"; DynamicUser = true; ExecStart = "${instanceConfig.pusher.package}/bin/workadventurepusher"; Restart = "always"; RestartSec = "10s"; }; }; }) cfg.instances; frontPackage = mapAttrs (instanceName: instanceConfig: instanceConfig.frontend.package.override { settings = { apiUrl = (urls instanceConfig).api; uploaderUrl = (urls instanceConfig).uploader; adminUrl = (urls instanceConfig).admin; mapsUrl = (urls instanceConfig).maps; } // instanceConfig.frontend.settings; } ) cfg.instances; virtualHosts = mapAttrs (instanceName: instanceConfig: if instanceConfig.nginx.domain != null then { default = instanceConfig.nginx.default; serverName = instanceConfig.nginx.domain; root = frontPackage.${instanceName} + "/dist"; locations = { "/_/" = { tryFiles = "/index.html =404"; }; "/pusher/" = { #proxyPass = "http://10.233.3.1:9000"; proxyPass = "http://localhost:${toString instanceConfig.pusher.port}/"; proxyWebsockets = true; }; "/maps/" = mkIf instanceConfig.nginx.serveDefaultMaps { alias = instanceConfig.maps.path; }; }; } else # TODO: Configuration with separate domains is unsupported for now. # Not sure if there's any interest in that anyway. builtins.throw "Configurations with separate domains are not supported yet" ) cfg.instances; in { options = { services.workadventure = rec { instances = mkOption { type = types.attrsOf (types.submodule (import ./instance-options.nix { inherit config lib pkgs; })); default = {}; description = "Declarative WorkAdventure instance config"; }; nginx = { enable = mkOption { default = true; type = types.bool; description = "Whether to enable nginx and configure it to serve the instances"; }; }; }; }; config = { assertions = mapAttrsToList (name: instance: { assertion = !cfg.nginx.enable || (instance.nginx.domain != null && all (d: d == null) (attrValues instance.nginx.domains)) || (instance.nginx.domain == null && all (d: d != null) (attrValues instance.nginx.domains)); message = "In instance ${name}, you have to either define nginx.domain or all attributes of nginx.domains"; }) cfg.instances; systemd.services = servicesBack // servicesPusher; services.nginx = mkIf cfg.nginx.enable { inherit virtualHosts; enable = mkDefault true; }; nixpkgs.overlays = [ workadventureOverlay ]; }; }