aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorstuebinm2021-02-05 13:31:42 +0100
committerstuebinm2021-02-05 13:36:17 +0100
commit86c56bb9a40425e4567c3648d427ad7e6be01a65 (patch)
tree814908629b856c99dadc32773ad2dec586160cca
Functional module, extracted from fediventure repo
(just to make it easier to hack, and remove fediventure-specific deployment logic)
-rw-r--r--instance-options.nix143
-rw-r--r--workadventure-nix.nix33
-rw-r--r--workadventure.nix138
3 files changed, 314 insertions, 0 deletions
diff --git a/instance-options.nix b/instance-options.nix
new file mode 100644
index 0000000..6a1d2dc
--- /dev/null
+++ b/instance-options.nix
@@ -0,0 +1,143 @@
+# Configuration options specific to a single workadventure instance.
+
+{ lib, config, ... }:
+
+with lib;
+let workadventure = import ./workadventure-nix.nix { inherit lib; };
+in
+{
+ options = rec {
+ backend = {
+ httpPort = mkOption {
+ default = 8081;
+ type = types.ints.u16;
+ description = "The TCP port the backend will bind to for http";
+ };
+
+ grpcPort = mkOption {
+ default = 50051;
+ type = types.ints.u16;
+ description = "The TCP port the backend will bind to for grpc";
+ };
+
+ package = mkOption {
+ default = workadventure.back;
+ defaultText = "third_party.workadventure-nix.back";
+ type = types.package;
+ description = "Backend package to use";
+ };
+ };
+
+ pusher = {
+ port = mkOption {
+ default = 8080;
+ type = types.ints.u16;
+ description = "The TCP port the pusher will bind to";
+ };
+
+ package = mkOption {
+ default = workadventure.pusher;
+ defaultText = "third_party.workadventure-nix.pusher";
+ type = types.package;
+ description = "Pusher package to use";
+ };
+ };
+
+ frontend = {
+ package = mkOption {
+ default = workadventure.front;
+ defaultText = "third_party.workadventure-nix.front";
+ type = types.package;
+ description = "Front package to use";
+ };
+
+ defaultMap = mkOption {
+ default = null;
+ defaultText = "not set";
+ type = types.nullOr types.str;
+ description = "The url to the default map, which will be loaded if none is given in the url. Must be a reachable url relative to the public map url defined in `maps.url`.";
+ };
+
+ urls = {
+ api = mkOption {
+ default = "/pusher";
+ type = types.str;
+ description = "The base url for the api, from the browser's point of view";
+ };
+
+ uploader = mkOption {
+ default = "/uploader";
+ type = types.str;
+ description = "The base url for the uploader, from the browser's point of view";
+ };
+
+ admin = mkOption {
+ default = "/admin";
+ type = types.str;
+ description = "The base url for the admin, from the browser's point of view";
+ };
+
+ maps = mkOption {
+ default = "/maps";
+ type = types.str;
+ description = "The base url for serving maps, from the browser's point of view";
+ };
+ };
+ };
+
+ maps = {
+ path = mkOption {
+ default = workadventure.maps.outPath + "/workadventuremaps/";
+ defaultText = "third_party.workadventure-nix.maps";
+ type = types.path;
+ description = "Maps package to use";
+ };
+ };
+
+ nginx = {
+ default = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether this instance will be the default one served by nginx";
+ };
+
+ domain = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "The domain name to serve workadenture services under. Mutually exclusive with domains.X";
+ };
+
+ serveDefaultMaps = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether to serve the maps provided by workadventure";
+ };
+
+ domains = {
+ back = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "The domain name to serve the backend under";
+ };
+
+ pusher = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "The domain name to serve the pusher under";
+ };
+
+ maps = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "The domain name to serve the maps under";
+ };
+
+ front = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "The domain name to serve the front under";
+ };
+ };
+ };
+ };
+}
diff --git a/workadventure-nix.nix b/workadventure-nix.nix
new file mode 100644
index 0000000..4c515cb
--- /dev/null
+++ b/workadventure-nix.nix
@@ -0,0 +1,33 @@
+# WorkAdventure packaging effort by SuperSandro2000, not yet upstreamed into nixpkgs.
+
+{ lib, ... }:
+
+
+let
+ pkgs = import <nixpkgs> {};
+
+ src = pkgs.fetchgit {
+ url = "https://gitlab.infra4future.de/stuebinm/workadventure-nix";
+ rev = "71ed23142c5ab6db05263b6e5c52f8fab1d84425";
+ sha256 = "0g20rzaxp5md26hc3dig4hhp296bd45n1zi3b67a8q0l290ydn2g";
+ };
+
+ # Use a fixed-point operator to build a nixpkgs-like structure that contains all
+ # workadventure derivation.
+ wapkgs = lib.fix (self: let
+ callPackage = lib.callPackageWith (pkgs // self);
+ in {
+ workadventure-pusher = callPackage "${src}/pusher" {};
+ workadventure-back = callPackage "${src}/back" {};
+ workadventure-front = callPackage "${src}/front" {};
+ workadventure-messages = callPackage "${src}/messages" {};
+ workadventure-maps = callPackage "${src}/maps" {};
+ });
+
+# Build public attrset of all accessible components.
+in rec {
+ pusher = wapkgs.workadventure-pusher;
+ back = wapkgs.workadventure-back;
+ front = wapkgs.workadventure-front;
+ maps = wapkgs.workadventure-maps;
+}
diff --git a/workadventure.nix b/workadventure.nix
new file mode 100644
index 0000000..02f9803
--- /dev/null
+++ b/workadventure.nix
@@ -0,0 +1,138 @@
+# Workadventure NixOS module. Used to deploy fediventure-compatible instances.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.workadventure;
+
+ 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;
+ };
+ 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}";
+ };
+ 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 {
+ environment = {
+ API_URL = instanceConfig.frontend.urls.api;
+ UPLOADER_URL = instanceConfig.frontend.urls.uploader;
+ ADMIN_URL = instanceConfig.frontend.urls.admin;
+ MAPS_URL = instanceConfig.frontend.urls.maps;
+ } // (if instanceConfig.frontend.defaultMap == null then {} else { DEFAULT_MAP_URL = instanceConfig.frontend.defaultMap; });
+ }
+ ) 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://localhost:${toString instanceConfig.pusher.port}/";
+ };
+
+ "/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;
+ }));
+ 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;
+ };
+ };
+}