Compare commits

...

16 Commits

8 changed files with 175 additions and 112 deletions

View File

@@ -40,6 +40,25 @@
}; };
services.nginx.virtualHosts."_" = lib.mkIf config.services.nginx.enable { services.nginx.virtualHosts."_" = lib.mkIf config.services.nginx.enable {
listen = [
{
addr = "0.0.0.0";
extraParameters = [
"default_server"
# Seemingly the default value of net.core.somaxconn
"backlog=4096"
"deferred"
];
}
{
addr = "[::0]";
extraParameters = [
"default_server"
"backlog=4096"
"deferred"
];
}
];
sslCertificate = "/etc/certs/nginx.crt"; sslCertificate = "/etc/certs/nginx.crt";
sslCertificateKey = "/etc/certs/nginx.key"; sslCertificateKey = "/etc/certs/nginx.key";
addSSL = true; addSSL = true;

View File

@@ -137,6 +137,7 @@
]; ];
modules = [ modules = [
inputs.pvv-nettsiden.nixosModules.default inputs.pvv-nettsiden.nixosModules.default
self.nixosModules.bluemap
]; ];
}; };
ildkule = stableNixosConfig "ildkule" { }; ildkule = stableNixosConfig "ildkule" { };
@@ -193,6 +194,7 @@
})); }));
nixosModules = { nixosModules = {
bluemap = ./modules/bluemap.nix;
snakeoil-certs = ./modules/snakeoil-certs.nix; snakeoil-certs = ./modules/snakeoil-certs.nix;
snappymail = ./modules/snappymail.nix; snappymail = ./modules/snappymail.nix;
robots-txt = ./modules/robots-txt.nix; robots-txt = ./modules/robots-txt.nix;
@@ -225,6 +227,8 @@
simplesamlphp = pkgs.callPackage ./packages/simplesamlphp { }; simplesamlphp = pkgs.callPackage ./packages/simplesamlphp { };
bluemap = pkgs.callPackage ./packages/bluemap.nix { };
out-of-your-element = pkgs.callPackage ./packages/out-of-your-element.nix { }; out-of-your-element = pkgs.callPackage ./packages/out-of-your-element.nix { };
} // } //
(lib.pipe null [ (lib.pipe null [

View File

@@ -6,7 +6,7 @@
(fp /base) (fp /base)
(fp /misc/metrics-exporters.nix) (fp /misc/metrics-exporters.nix)
./services/bluemap/default.nix ./services/bluemap.nix
./services/idp-simplesamlphp ./services/idp-simplesamlphp
./services/kerberos.nix ./services/kerberos.nix
./services/mediawiki ./services/mediawiki

View File

@@ -3,10 +3,7 @@ let
vanillaSurvival = "/var/lib/bluemap/vanilla_survival_world"; vanillaSurvival = "/var/lib/bluemap/vanilla_survival_world";
format = pkgs.formats.hocon { }; format = pkgs.formats.hocon { };
in { in {
imports = [ # NOTE: our versino of the module gets added in flake.nix
./module.nix # From danio, pending upstreaming
];
disabledModules = [ "services/web-apps/bluemap.nix" ]; disabledModules = [ "services/web-apps/bluemap.nix" ];
sops.secrets."bluemap/ssh-key" = { }; sops.secrets."bluemap/ssh-key" = { };
@@ -14,7 +11,6 @@ in {
services.bluemap = { services.bluemap = {
enable = true; enable = true;
package = pkgs.callPackage ./package.nix { };
eula = true; eula = true;
onCalendar = "*-*-* 05:45:00"; # a little over an hour after auto-upgrade onCalendar = "*-*-* 05:45:00"; # a little over an hour after auto-upgrade
@@ -27,7 +23,13 @@ in {
"verden" = { "verden" = {
settings = { settings = {
world = vanillaSurvival; world = vanillaSurvival;
dimension = "minecraft:overworld";
name = "Verden";
sorting = 0; sorting = 0;
start-pos = {
x = 0;
z = 0;
};
ambient-light = 0.1; ambient-light = 0.1;
cave-detection-ocean-floor = -5; cave-detection-ocean-floor = -5;
marker-sets = { marker-sets = {
@@ -37,16 +39,24 @@ in {
}; };
"underverden" = { "underverden" = {
settings = { settings = {
world = "${vanillaSurvival}/DIM-1"; world = vanillaSurvival;
dimension = "minecraft:the_nether";
name = "Underverden";
sorting = 100; sorting = 100;
start-pos = {
x = 0;
z = 0;
};
sky-color = "#290000"; sky-color = "#290000";
void-color = "#150000"; void-color = "#150000";
sky-light = 1;
ambient-light = 0.6; ambient-light = 0.6;
world-sky-light = 0;
remove-caves-below-y = -10000; remove-caves-below-y = -10000;
cave-detection-ocean-floor = -5; cave-detection-ocean-floor = -5;
cave-detection-uses-block-light = true; cave-detection-uses-block-light = true;
max-y = 90; render-mask = [{
max-y = 90;
}];
marker-sets = { marker-sets = {
_includes = [ (format.lib.mkInclude "${bluemap-export}/nether.hocon") ]; _includes = [ (format.lib.mkInclude "${bluemap-export}/nether.hocon") ];
}; };
@@ -54,12 +64,18 @@ in {
}; };
"enden" = { "enden" = {
settings = { settings = {
world = "${vanillaSurvival}/DIM1"; world = vanillaSurvival;
dimension = "minecraft:the_end";
name = "Enden";
sorting = 200; sorting = 200;
start-pos = {
x = 0;
z = 0;
};
sky-color = "#080010"; sky-color = "#080010";
void-color = "#080010"; void-color = "#080010";
sky-light = 1;
ambient-light = 0.6; ambient-light = 0.6;
world-sky-light = 0;
remove-caves-below-y = -10000; remove-caves-below-y = -10000;
cave-detection-ocean-floor = -5; cave-detection-ocean-floor = -5;
marker-sets = { marker-sets = {
@@ -70,26 +86,42 @@ in {
}; };
}; };
services.nginx.virtualHosts."minecraft.pvv.ntnu.no" = {
enableACME = true;
forceSSL = true;
};
# TODO: render somewhere else lmao
systemd.services."render-bluemap-maps" = { systemd.services."render-bluemap-maps" = {
preStart = ''
mkdir -p /var/lib/bluemap/world
${pkgs.rsync}/bin/rsync \
-e "${pkgs.openssh}/bin/ssh -o UserKnownHostsFile=$CREDENTIALS_DIRECTORY/ssh-known-hosts -i $CREDENTIALS_DIRECTORY/sshkey" \
-avz --no-owner --no-group \
root@innovation.pvv.ntnu.no:/ \
${vanillaSurvival}
'';
serviceConfig = { serviceConfig = {
StateDirectory = [ "bluemap/world" ];
ExecStartPre = let
rsyncArgs = lib.cli.toCommandLineShellGNU { } {
archive = true;
compress = true;
verbose = true;
no-owner = true;
no-group = true;
rsh = "${pkgs.openssh}/bin/ssh -o UserKnownHostsFile=%d/ssh-known-hosts -i %d/sshkey";
};
in "${lib.getExe pkgs.rsync} ${rsyncArgs} root@innovation.pvv.ntnu.no:/ ${vanillaSurvival}";
LoadCredential = [ LoadCredential = [
"sshkey:${config.sops.secrets."bluemap/ssh-key".path}" "sshkey:${config.sops.secrets."bluemap/ssh-key".path}"
"ssh-known-hosts:${config.sops.secrets."bluemap/ssh-known-hosts".path}" "ssh-known-hosts:${config.sops.secrets."bluemap/ssh-known-hosts".path}"
]; ];
}; };
}; };
services.nginx.virtualHosts."minecraft.pvv.ntnu.no" = {
enableACME = true;
forceSSL = true;
kTLS = true;
http3 = true;
quic = true;
http3_hq = true;
extraConfig = ''
# Enabling QUIC 0-RTT
ssl_early_data on;
quic_gso on;
quic_retry on;
add_header Alt-Svc 'h3=":$server_port"; ma=86400';
'';
};
networking.firewall.allowedUDPPorts = [ 443 ];
} }

View File

@@ -1,30 +0,0 @@
{ lib, stdenvNoCC, fetchurl, makeWrapper, jre }:
stdenvNoCC.mkDerivation rec {
pname = "bluemap";
version = "5.7";
src = fetchurl {
url = "https://github.com/BlueMap-Minecraft/BlueMap/releases/download/v${version}/BlueMap-${version}-cli.jar";
hash = "sha256-8udZYJgrr4bi2mjRYrASd8JwUoUVZW1tZpOLRgafAIw=";
};
dontUnpack = true;
nativeBuildInputs = [ makeWrapper ];
installPhase = ''
runHook preInstall
makeWrapper ${jre}/bin/java $out/bin/bluemap --add-flags "-jar $src"
runHook postInstall
'';
meta = {
description = "3D minecraft map renderer";
homepage = "https://bluemap.bluecolored.de/";
sourceProvenance = with lib.sourceTypes; [ binaryBytecode ];
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ dandellion h7x4 ];
mainProgram = "bluemap";
};
}

View File

@@ -25,7 +25,7 @@ let
"core.conf" = coreConfig; "core.conf" = coreConfig;
"webapp.conf" = webappConfig; "webapp.conf" = webappConfig;
"webserver.conf" = webserverConfig; "webserver.conf" = webserverConfig;
"packs" = cfg.resourcepacks; "packs" = cfg.packs;
}; };
renderConfigFolder = name: value: pkgs.linkFarm "bluemap-${name}-config" { renderConfigFolder = name: value: pkgs.linkFarm "bluemap-${name}-config" {
@@ -36,7 +36,7 @@ let
"core.conf" = coreConfig; "core.conf" = coreConfig;
"webapp.conf" = format.generate "webapp.conf" (cfg.webappSettings // { "update-settings-file" = false; }); "webapp.conf" = format.generate "webapp.conf" (cfg.webappSettings // { "update-settings-file" = false; });
"webserver.conf" = webserverConfig; "webserver.conf" = webserverConfig;
"packs" = value.resourcepacks; "packs" = value.packs;
}; };
inherit (lib) mkOption; inherit (lib) mkOption;
@@ -110,7 +110,7 @@ in {
metrics = lib.mkEnableOption "Sending usage metrics containing the version of bluemap in use"; metrics = lib.mkEnableOption "Sending usage metrics containing the version of bluemap in use";
}; };
}; };
description = "Settings for the core.conf file, [see upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/core.conf)."; description = "Settings for the core.conf file, [see upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/common/src/main/resources/de/bluecolored/bluemap/config/core.conf).";
}; };
webappSettings = mkOption { webappSettings = mkOption {
@@ -127,7 +127,7 @@ in {
webroot = config.services.bluemap.webRoot; webroot = config.services.bluemap.webRoot;
} }
''; '';
description = "Settings for the webapp.conf file, see [upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/webapp.conf)."; description = "Settings for the webapp.conf file, see [upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/common/src/main/resources/de/bluecolored/bluemap/config/webapp.conf).";
}; };
webserverSettings = mkOption { webserverSettings = mkOption {
@@ -147,18 +147,18 @@ in {
default = { }; default = { };
description = '' description = ''
Settings for the webserver.conf file, usually not required. Settings for the webserver.conf file, usually not required.
[See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/webserver.conf). [See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/common/src/main/resources/de/bluecolored/bluemap/config/webserver.conf).
''; '';
}; };
maps = mkOption { maps = mkOption {
type = lib.types.attrsOf (lib.types.submodule { type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
options = { options = {
resourcepacks = mkOption { packs = mkOption {
type = lib.types.path; type = lib.types.path;
default = cfg.resourcepacks; default = cfg.packs;
defaultText = lib.literalExpression "config.services.bluemap.resourcepacks"; defaultText = lib.literalExpression "config.services.bluemap.packs";
description = "A set of resourcepacks/mods/bluemap-addons to extract models from loaded in alphabetical order"; description = "A set of resourcepacks, datapacks, and mods to extract resources from, loaded in alphabetical order.";
}; };
settings = mkOption { settings = mkOption {
type = (lib.types.submodule { type = (lib.types.submodule {
@@ -168,43 +168,74 @@ in {
type = lib.types.path; type = lib.types.path;
description = "Path to world folder containing the dimension to render"; description = "Path to world folder containing the dimension to render";
}; };
name = mkOption {
type = lib.types.str;
description = "The display name of this map (how this map will be named on the webapp)";
default = name;
defaultText = lib.literalExpression "<name>";
};
render-mask = mkOption {
type = with lib.types; listOf (attrsOf format.type);
description = "Limits for the map render";
default = [ ];
example = [
{
min-x = -4000;
max-x = 4000;
min-z = -4000;
max-z = 4000;
min-y = 50;
max-y = 100;
}
{
subtract = true;
min-y = 90;
max-y = 127;
}
];
};
}; };
}); });
description = '' description = ''
Settings for files in `maps/`. Settings for files in `maps/`.
See the default for an example with good options for the different world types. See the default for an example with good options for the different world types.
For valid values [consult upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/maps/map.conf). For valid values [consult upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/common/src/main/resources/de/bluecolored/bluemap/config/maps/map.conf).
''; '';
}; };
}; };
}); }));
default = { default = {
"overworld".settings = { "overworld".settings = {
world = "${cfg.defaultWorld}"; world = cfg.defaultWorld;
dimension = "minecraft:overworld";
name = "Overworld";
ambient-light = 0.1; ambient-light = 0.1;
cave-detection-ocean-floor = -5; cave-detection-ocean-floor = -5;
}; };
"nether".settings = { "nether".settings = {
world = "${cfg.defaultWorld}/DIM-1"; world = cfg.defaultWorld;
dimension = "minecraft:the_nether";
name = "Nether";
sorting = 100; sorting = 100;
sky-color = "#290000"; sky-color = "#290000";
void-color = "#150000"; void-color = "#150000";
sky-light = 1;
ambient-light = 0.6; ambient-light = 0.6;
world-sky-light = 0;
remove-caves-below-y = -10000; remove-caves-below-y = -10000;
cave-detection-ocean-floor = -5; cave-detection-ocean-floor = -5;
cave-detection-uses-block-light = true; cave-detection-uses-block-light = true;
max-y = 90;
}; };
"end".settings = { "end".settings = {
world = "${cfg.defaultWorld}/DIM1"; world = cfg.defaultWorld;
dimension = "minecraft:the_end";
name = "The End";
sorting = 200; sorting = 200;
sky-color = "#080010"; sky-color = "#080010";
void-color = "#080010"; void-color = "#080010";
sky-light = 1;
ambient-light = 0.6; ambient-light = 0.6;
world-sky-light = 0;
remove-caves-below-y = -10000; remove-caves-below-y = -10000;
cave-detection-ocean-floor = -5; cave-detection-ocean-floor = -5;
}; };
@@ -212,31 +243,36 @@ in {
defaultText = lib.literalExpression '' defaultText = lib.literalExpression ''
{ {
"overworld".settings = { "overworld".settings = {
world = "''${cfg.defaultWorld}"; world = cfg.defaultWorld;
name = "Overworld";
dimension = "minecraft:overworld";
ambient-light = 0.1; ambient-light = 0.1;
cave-detection-ocean-floor = -5; cave-detection-ocean-floor = -5;
}; };
"nether".settings = { "nether".settings = {
world = "''${cfg.defaultWorld}/DIM-1"; world = cfg.defaultWorld;
dimension = "minecraft:the_nether";
name = "Nether";
sorting = 100; sorting = 100;
sky-color = "#290000"; sky-color = "#290000";
void-color = "#150000"; void-color = "#150000";
sky-light = 1;
ambient-light = 0.6; ambient-light = 0.6;
world-sky-light = 0;
remove-caves-below-y = -10000; remove-caves-below-y = -10000;
cave-detection-ocean-floor = -5; cave-detection-ocean-floor = -5;
cave-detection-uses-block-light = true; cave-detection-uses-block-light = true;
max-y = 90;
}; };
"end".settings = { "end".settings = {
world = "''${cfg.defaultWorld}/DIM1"; world = cfg.defaultWorld;
name = "The End";
dimension = "minecraft:the_end";
sorting = 200; sorting = 200;
sky-color = "#080010"; sky-color = "#080010";
void-color = "#080010"; void-color = "#080010";
sky-light = 1;
ambient-light = 0.6; ambient-light = 0.6;
world-sky-light = 0;
remove-caves-below-y = -10000; remove-caves-below-y = -10000;
cave-detection-ocean-floor = -5; cave-detection-ocean-floor = -5;
}; };
@@ -264,7 +300,7 @@ in {
description = '' description = ''
Where the rendered map will be stored. Where the rendered map will be stored.
Unless you are doing something advanced you should probably leave this alone and configure webRoot instead. Unless you are doing something advanced you should probably leave this alone and configure webRoot instead.
[See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/tree/master/BlueMapCommon/src/main/resources/de/bluecolored/bluemap/config/storages) [See upstream docs](https://github.com/BlueMap-Minecraft/BlueMap/tree/master/common/src/main/resources/de/bluecolored/bluemap/config/storages)
''; '';
default = { default = {
"file" = { "file" = {
@@ -280,12 +316,12 @@ in {
''; '';
}; };
resourcepacks = mkOption { packs = mkOption {
type = lib.types.path; type = lib.types.path;
default = pkgs.linkFarm "resourcepacks" { }; default = pkgs.linkFarm "packs" { };
description = '' description = ''
A set of resourcepacks/mods to extract models from loaded in alphabetical order. A set of resourcepacks, datapacks, and mods to extract resources from, loaded in alphabetical order.
Can be overriden on a per-map basis with `services.bluemap.maps.<name>.resourcepacks`. Can be overriden on a per-map basis with `services.bluemap.maps.<name>.packs`.
''; '';
}; };
}; };
@@ -306,21 +342,23 @@ in {
systemd.services."render-bluemap-maps" = lib.mkIf cfg.enableRender { systemd.services."render-bluemap-maps" = lib.mkIf cfg.enableRender {
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
CPUSchedulingPolicy = "batch";
Group = "nginx"; Group = "nginx";
UMask = "026"; UMask = "026";
ExecStart = [
# If web folder doesnt exist generate it
''|test -f "${cfg.webRoot}" || ${lib.getExe cfg.package} -c ${webappConfigFolder} -gs''
]
++
# Render each minecraft map
lib.attrsets.mapAttrsToList
(name: value: "${lib.getExe cfg.package} -c ${renderConfigFolder name value} -r")
cfg.maps
++ [
# Generate updated webapp
"${lib.getExe cfg.package} -c ${webappConfigFolder} -gs"
];
}; };
script = ''
# If web folder doesnt exist generate it
test -f "${cfg.webRoot}" || ${lib.getExe cfg.package} -c ${webappConfigFolder} -gs
# Render each minecraft map
${lib.strings.concatStringsSep "\n" (lib.attrsets.mapAttrsToList
(name: value: "${lib.getExe cfg.package} -c ${renderConfigFolder name value} -r")
cfg.maps)}
# Generate updated webapp
${lib.getExe cfg.package} -c ${webappConfigFolder} -gs
'';
}; };
systemd.timers."render-bluemap-maps" = lib.mkIf cfg.enableRender { systemd.timers."render-bluemap-maps" = lib.mkIf cfg.enableRender {

View File

@@ -2,11 +2,11 @@
stdenvNoCC.mkDerivation rec { stdenvNoCC.mkDerivation rec {
pname = "bluemap"; pname = "bluemap";
version = "5.2"; version = "5.15";
src = fetchurl { src = fetchurl {
url = "https://github.com/BlueMap-Minecraft/BlueMap/releases/download/v${version}/BlueMap-${version}-cli.jar"; url = "https://github.com/BlueMap-Minecraft/BlueMap/releases/download/v${version}/BlueMap-${version}-cli.jar";
hash = "sha256-4vld+NBwzBxdwbMtsKuqvO6immkbh4HB//6wdjXaxoU="; hash = "sha256-g50V/4LtHaHNRMTt+PK/ZTf4Tber2D6ZHJvuAXQLaFI=";
}; };
dontUnpack = true; dontUnpack = true;

View File

@@ -33,13 +33,13 @@ in
lib.mergeAttrsList [ lib.mergeAttrsList [
(mw-ext { (mw-ext {
name = "CodeEditor"; name = "CodeEditor";
commit = "9f19fe510beb671d6ea3076e2e7cbd1025451924"; commit = "6e5b06e8cf2d040c0abb53ac3735f9f3c96a7a4f";
hash = "sha256-Bl0evDM4TpsoU5gvZ02UaH5ehFatJcn8YJPbUWRcK9s="; hash = "sha256-Jee+Ws9REUohywhbuemixXKaTRc54+cIlyUNDCyYcEM=";
}) })
(mw-ext { (mw-ext {
name = "CodeMirror"; name = "CodeMirror";
commit = "050d8257c942dfd95b98525c0a61290a89fe8ef4"; commit = "da9c5d4f03e6425f6f2cf68b75d21311e0f7e77e";
hash = "sha256-3DnY9wlaG9BrnSgt8GMM6fzp3nAAPno49vr2QAz50Ho="; hash = "sha256-aL+v9xeqKHGmQVUWVczh54BkReu+fP49PT1NP7eTC6k=";
}) })
(mw-ext { (mw-ext {
name = "DeleteBatch"; name = "DeleteBatch";
@@ -53,13 +53,13 @@ lib.mergeAttrsList [
}) })
(mw-ext { (mw-ext {
name = "Popups"; name = "Popups";
commit = "4c22b8604b0dca04f001d9e2bc13b1ea4f934835"; commit = "7ed940a09f83f869cbc0bc20f3ca92f85b534951";
hash = "sha256-mul9m5zPFSBCfBHZJihJrxP55kFMo/YJ18+JLt5X6zA="; hash = "sha256-pcDPcu4kSvMHfSOuShrod694TKI9Oo3AEpMP9DXp9oY=";
}) })
(mw-ext { (mw-ext {
name = "Scribunto"; name = "Scribunto";
commit = "4a917ed13212f60c33dbc82d3d18c7f5b8461fdc"; commit = "e755852a8e28a030a21ded2d5dd7270eb933b683";
hash = "sha256-3qQgXyPb00V9McN8fxgZlU+MeBzQm5ikH/vkXazibY8="; hash = "sha256-zyI5nSE+KuodJOWyV0CQM7G0GfkKEgfoF/czi2/qk98=";
}) })
(mw-ext { (mw-ext {
name = "SimpleSAMLphp"; name = "SimpleSAMLphp";
@@ -69,8 +69,8 @@ lib.mergeAttrsList [
}) })
(mw-ext { (mw-ext {
name = "TemplateData"; name = "TemplateData";
commit = "1b02875f3e668fa9033849a663c5f5e450581071"; commit = "fd7cf4d95a70ef564130266f2a6b18f33a2a2ff9";
hash = "sha256-vQ/o7X7puTN1OQzX3bwKsW3IyVbK1IzvQKV9KtV2kRA="; hash = "sha256-5OhDPFhIi55Eh5+ovMP1QTjNBb9Sm/3vyArNCApAgSw=";
}) })
(mw-ext { (mw-ext {
name = "TemplateStyles"; name = "TemplateStyles";
@@ -84,12 +84,12 @@ lib.mergeAttrsList [
}) })
(mw-ext { (mw-ext {
name = "VisualEditor"; name = "VisualEditor";
commit = "3cca60141dec1150d3019bd14bd9865cf120362d"; commit = "032364cfdff33818e6ae0dfa251fe3973b0ae4f3";
hash = "sha256-HwbmRVaQObYoJdABeHn19WBoq8aw+Q6QU8xr9YvDcJU="; hash = "sha256-AQDdq9r6rSo8h4u1ERonH14/1i1BgLGdzANEiQ065PU=";
}) })
(mw-ext { (mw-ext {
name = "WikiEditor"; name = "WikiEditor";
commit = "d5e6856eeba114fcd1653f3e7ae629989f5ced56"; commit = "cb9f7e06a9c59b6d3b31c653e5886b7f53583d01";
hash = "sha256-U5ism/ni9uAxiD4wOVE0/8FFUc4zQCPqYmQ1AL5+E7Q="; hash = "sha256-UWi3Ac+LCOLliLkXnS8YL0rD/HguuPH5MseqOm0z7s4=";
}) })
] ]