diff --git a/flake.lock b/flake.lock index fc6f9369..e512693a 100644 --- a/flake.lock +++ b/flake.lock @@ -121,6 +121,21 @@ "type": "github" } }, + "minecraft-data": { + "locked": { + "lastModified": 1725277886, + "narHash": "sha256-Fw4VbbE3EfypQWSgPDFfvVH47BHeg3ptsO715NlUM8Q=", + "ref": "refs/heads/master", + "rev": "1b4087bd3322a2e2ba84271c8fcc013e6b641a58", + "revCount": 2, + "type": "git", + "url": "https://git.pvv.ntnu.no/Drift/minecraft-data.git" + }, + "original": { + "type": "git", + "url": "https://git.pvv.ntnu.no/Drift/minecraft-data.git" + } + }, "nix-gitea-themes": { "inputs": { "nixpkgs": [ @@ -233,6 +248,7 @@ "grzegorz": "grzegorz", "grzegorz-clients": "grzegorz-clients", "matrix-next": "matrix-next", + "minecraft-data": "minecraft-data", "nix-gitea-themes": "nix-gitea-themes", "nixpkgs": "nixpkgs", "nixpkgs-unstable": "nixpkgs-unstable", diff --git a/flake.nix b/flake.nix index ac2839d1..a8a26adb 100644 --- a/flake.nix +++ b/flake.nix @@ -27,6 +27,8 @@ grzegorz.inputs.nixpkgs.follows = "nixpkgs-unstable"; grzegorz-clients.url = "github:Programvareverkstedet/grzegorz-clients"; grzegorz-clients.inputs.nixpkgs.follows = "nixpkgs"; + + minecraft-data.url = "git+https://git.pvv.ntnu.no/Drift/minecraft-data.git"; }; outputs = { self, nixpkgs, nixpkgs-unstable, sops-nix, disko, ... }@inputs: @@ -92,6 +94,7 @@ heimdal = unstablePkgs.heimdal; mediawiki-extensions = final.callPackage ./packages/mediawiki-extensions { }; simplesamlphp = final.callPackage ./packages/simplesamlphp { }; + bluemap = final.callPackage ./packages/bluemap.nix { }; }) inputs.nix-gitea-themes.overlays.default inputs.pvv-nettsiden.overlays.default diff --git a/hosts/bekkalokk/configuration.nix b/hosts/bekkalokk/configuration.nix index 7cf4a7e8..f8971a68 100644 --- a/hosts/bekkalokk/configuration.nix +++ b/hosts/bekkalokk/configuration.nix @@ -6,6 +6,7 @@ ../../base ../../misc/metrics-exporters.nix + ./services/bluemap/default.nix ./services/gitea/default.nix ./services/idp-simplesamlphp ./services/kerberos diff --git a/hosts/bekkalokk/services/bluemap/default.nix b/hosts/bekkalokk/services/bluemap/default.nix new file mode 100644 index 00000000..e8ebe1f5 --- /dev/null +++ b/hosts/bekkalokk/services/bluemap/default.nix @@ -0,0 +1,83 @@ +{ config, lib, pkgs, inputs, ... }: +let + vanillaSurvival = "/var/lib/bluemap/vanilla_survival_world"; +in { + imports = [ + ./module.nix # From danio, pending upstreaming + ]; + + disabledModules = [ "services/web-servers/bluemap.nix" ]; + + sops.secrets."bluemap/ssh-key" = { }; + sops.secrets."bluemap/ssh-known-hosts" = { }; + + services.bluemap = { + enable = true; + eula = true; + onCalendar = "*-*-* 05:45:00"; # a little over an hour after auto-upgrade + + host = "minecraft.pvv.ntnu.no"; + + maps = { + "verden" = { + settings = { + world = vanillaSurvival; + sorting = 0; + ambient-light = 0.1; + cave-detection-ocean-floor = -5; + marker-sets = inputs.minecraft-data.map-markers.vanillaSurvival.verden; + }; + }; + "underverden" = { + settings = { + world = "${vanillaSurvival}/DIM-1"; + sorting = 100; + sky-color = "#290000"; + void-color = "#150000"; + ambient-light = 0.6; + world-sky-light = 0; + remove-caves-below-y = -10000; + cave-detection-ocean-floor = -5; + cave-detection-uses-block-light = true; + max-y = 90; + marker-sets = inputs.minecraft-data.map-markers.vanillaSurvival.underverden; + }; + }; + "enden" = { + settings = { + world = "${vanillaSurvival}/DIM1"; + sorting = 200; + sky-color = "#080010"; + void-color = "#080010"; + ambient-light = 0.6; + world-sky-light = 0; + remove-caves-below-y = -10000; + cave-detection-ocean-floor = -5; + }; + }; + }; + }; + + services.nginx.virtualHosts."minecraft.pvv.ntnu.no" = { + enableACME = true; + forceSSL = true; + }; + + # TODO: render somewhere else lmao + 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 = { + LoadCredential = [ + "sshkey:${config.sops.secrets."bluemap/ssh-key".path}" + "ssh-known-hosts:${config.sops.secrets."bluemap/ssh-known-hosts".path}" + ]; + }; + }; +} diff --git a/hosts/bekkalokk/services/bluemap/module.nix b/hosts/bekkalokk/services/bluemap/module.nix new file mode 100644 index 00000000..25ac6577 --- /dev/null +++ b/hosts/bekkalokk/services/bluemap/module.nix @@ -0,0 +1,343 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.bluemap; + format = pkgs.formats.hocon { }; + + coreConfig = format.generate "core.conf" cfg.coreSettings; + webappConfig = format.generate "webapp.conf" cfg.webappSettings; + webserverConfig = format.generate "webserver.conf" cfg.webserverSettings; + + storageFolder = pkgs.linkFarm "storage" + (lib.attrsets.mapAttrs' (name: value: + lib.nameValuePair "${name}.conf" + (format.generate "${name}.conf" value)) + cfg.storage); + + mapsFolder = pkgs.linkFarm "maps" + (lib.attrsets.mapAttrs' (name: value: + lib.nameValuePair "${name}.conf" + (format.generate "${name}.conf" value.settings)) + cfg.maps); + + webappConfigFolder = pkgs.linkFarm "bluemap-config" { + "maps" = mapsFolder; + "storages" = storageFolder; + "core.conf" = coreConfig; + "webapp.conf" = webappConfig; + "webserver.conf" = webserverConfig; + "packs" = cfg.resourcepacks; + "addons" = cfg.resourcepacks; # TODO + }; + + renderConfigFolder = name: value: pkgs.linkFarm "bluemap-${name}-config" { + "maps" = pkgs.linkFarm "maps" { + "${name}.conf" = (format.generate "${name}.conf" value.settings); + }; + "storages" = storageFolder; + "core.conf" = coreConfig; + "webapp.conf" = format.generate "webapp.conf" (cfg.webappSettings // { "update-settings-file" = false; }); + "webserver.conf" = webserverConfig; + "packs" = value.resourcepacks; + "addons" = cfg.resourcepacks; # TODO + }; + + inherit (lib) mkOption; +in { + options.services.bluemap = { + enable = lib.mkEnableOption "bluemap"; + + eula = mkOption { + type = lib.types.bool; + description = '' + By changing this option to true you confirm that you own a copy of minecraft Java Edition, + and that you agree to minecrafts EULA. + ''; + default = false; + }; + + defaultWorld = mkOption { + type = lib.types.path; + description = '' + The world used by the default map ruleset. + If you configure your own maps you do not need to set this. + ''; + example = lib.literalExpression "\${config.services.minecraft.dataDir}/world"; + }; + + enableRender = mkOption { + type = lib.types.bool; + description = "Enable rendering"; + default = true; + }; + + webRoot = mkOption { + type = lib.types.path; + default = "/var/lib/bluemap/web"; + description = "The directory for saving and serving the webapp and the maps"; + }; + + enableNginx = mkOption { + type = lib.types.bool; + default = true; + description = "Enable configuring a virtualHost for serving the bluemap webapp"; + }; + + host = mkOption { + type = lib.types.str; + default = "bluemap.${config.networking.domain}"; + defaultText = lib.literalExpression "bluemap.\${config.networking.domain}"; + description = "Domain to configure nginx for"; + }; + + onCalendar = mkOption { + type = lib.types.str; + description = '' + How often to trigger rendering the map, + in the format of a systemd timer onCalendar configuration. + See {manpage}`systemd.timer(5)`. + ''; + default = "*-*-* 03:10:00"; + }; + + coreSettings = mkOption { + type = lib.types.submodule { + freeformType = format.type; + options = { + data = mkOption { + type = lib.types.path; + description = "Folder for where bluemap stores its data"; + default = "/var/lib/bluemap"; + }; + 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)."; + }; + + webappSettings = mkOption { + type = lib.types.submodule { + freeformType = format.type; + }; + default = { + enabled = true; + webroot = cfg.webRoot; + }; + defaultText = lib.literalExpression '' + { + enabled = true; + 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)."; + }; + + webserverSettings = mkOption { + type = lib.types.submodule { + freeformType = format.type; + options = { + enabled = mkOption { + type = lib.types.bool; + description = '' + Enable bluemap's built-in webserver. + Disabled by default in nixos for use of nginx directly. + ''; + default = false; + }; + }; + }; + default = { }; + description = '' + 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). + ''; + }; + + maps = mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options = { + resourcepacks = mkOption { + type = lib.types.path; + default = cfg.resourcepacks; + defaultText = lib.literalExpression "config.services.bluemap.resourcepacks"; + description = "A set of resourcepacks/mods to extract models from loaded in alphabetical order"; + }; + settings = mkOption { + type = (lib.types.submodule { + freeformType = format.type; + options = { + world = mkOption { + type = lib.types.path; + description = "Path to world folder containing the dimension to render"; + }; + }; + }); + description = '' + Settings for files in `maps/`. + 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). + ''; + }; + }; + }); + default = { + "overworld".settings = { + world = "${cfg.defaultWorld}"; + ambient-light = 0.1; + cave-detection-ocean-floor = -5; + }; + + "nether".settings = { + world = "${cfg.defaultWorld}/DIM-1"; + sorting = 100; + sky-color = "#290000"; + void-color = "#150000"; + ambient-light = 0.6; + world-sky-light = 0; + remove-caves-below-y = -10000; + cave-detection-ocean-floor = -5; + cave-detection-uses-block-light = true; + max-y = 90; + }; + + "end".settings = { + world = "${cfg.defaultWorld}/DIM1"; + sorting = 200; + sky-color = "#080010"; + void-color = "#080010"; + ambient-light = 0.6; + world-sky-light = 0; + remove-caves-below-y = -10000; + cave-detection-ocean-floor = -5; + }; + }; + defaultText = lib.literalExpression '' + { + "overworld".settings = { + world = "''${cfg.defaultWorld}"; + ambient-light = 0.1; + cave-detection-ocean-floor = -5; + }; + + "nether".settings = { + world = "''${cfg.defaultWorld}/DIM-1"; + sorting = 100; + sky-color = "#290000"; + void-color = "#150000"; + ambient-light = 0.6; + world-sky-light = 0; + remove-caves-below-y = -10000; + cave-detection-ocean-floor = -5; + cave-detection-uses-block-light = true; + max-y = 90; + }; + + "end".settings = { + world = "''${cfg.defaultWorld}/DIM1"; + sorting = 200; + sky-color = "#080010"; + void-color = "#080010"; + ambient-light = 0.6; + world-sky-light = 0; + remove-caves-below-y = -10000; + cave-detection-ocean-floor = -5; + }; + }; + ''; + description = '' + map-specific configuration. + These correspond to views in the webapp and are usually + different dimension of a world or different render settings of the same dimension. + If you set anything in this option you must configure all dimensions yourself! + ''; + }; + + storage = mkOption { + type = lib.types.attrsOf (lib.types.submodule { + freeformType = format.type; + options = { + storage-type = mkOption { + type = lib.types.enum [ "FILE" "SQL" ]; + description = "Type of storage config"; + default = "FILE"; + }; + }; + }); + description = '' + Where the rendered map will be stored. + 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) + ''; + default = { + "file" = { + root = "${cfg.webRoot}/maps"; + }; + }; + defaultText = lib.literalExpression '' + { + "file" = { + root = "''${config.services.bluemap.webRoot}/maps"; + }; + } + ''; + }; + + resourcepacks = mkOption { + type = lib.types.path; + default = pkgs.linkFarm "resourcepacks" { }; + description = '' + A set of resourcepacks/mods to extract models from loaded in alphabetical order. + Can be overriden on a per-map basis with `services.bluemap.maps..resourcepacks`. + ''; + }; + }; + + + config = lib.mkIf cfg.enable { + assertions = + [ { assertion = config.services.bluemap.eula; + message = '' + You have enabled bluemap but have not accepted minecraft's EULA. + You can achieve this through setting `services.bluemap.eula = true` + ''; + } + ]; + + services.bluemap.coreSettings.accept-download = cfg.eula; + + systemd.services."render-bluemap-maps" = lib.mkIf cfg.enableRender { + serviceConfig = { + Type = "oneshot"; + Group = "nginx"; + UMask = "026"; + }; + script = lib.strings.concatStringsSep "\n" ((lib.attrsets.mapAttrsToList + (name: value: "${lib.getExe pkgs.bluemap} -c ${renderConfigFolder name value} -r") + cfg.maps) ++ [ "${lib.getExe pkgs.bluemap} -c ${webappConfigFolder} -gs" ]); + }; + + systemd.timers."render-bluemap-maps" = lib.mkIf cfg.enableRender { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = cfg.onCalendar; + Persistent = true; + Unit = "render-bluemap-maps.service"; + }; + }; + + services.nginx.virtualHosts = lib.mkIf cfg.enableNginx { + "${cfg.host}" = { + root = config.services.bluemap.webRoot; + locations = { + "~* ^/maps/[^/]*/tiles/".extraConfig = '' + error_page 404 = @empty; + ''; + "@empty".return = "204"; + }; + }; + }; + }; + + meta = { + maintainers = with lib.maintainers; [ dandellion h7x4 ]; + }; +} diff --git a/packages/bluemap.nix b/packages/bluemap.nix new file mode 100644 index 00000000..7463a3e2 --- /dev/null +++ b/packages/bluemap.nix @@ -0,0 +1,30 @@ +{ lib, stdenvNoCC, fetchurl, makeWrapper, jre }: + +stdenvNoCC.mkDerivation rec { + pname = "bluemap"; + version = "5.2"; + + src = fetchurl { + url = "https://github.com/BlueMap-Minecraft/BlueMap/releases/download/v${version}/BlueMap-${version}-cli.jar"; + hash = "sha256-4vld+NBwzBxdwbMtsKuqvO6immkbh4HB//6wdjXaxoU="; + }; + + 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 ]; + mainProgram = "bluemap"; + }; +} diff --git a/secrets/bekkalokk/bekkalokk.yaml b/secrets/bekkalokk/bekkalokk.yaml index c315b7ef..d77a1516 100644 --- a/secrets/bekkalokk/bekkalokk.yaml +++ b/secrets/bekkalokk/bekkalokk.yaml @@ -32,6 +32,9 @@ nettsiden: admin_password: ENC[AES256_GCM,data:SADr/zN3F0tW339kSK1nD9Pb38rw7hz8,iv:s5jgl1djXd5JKwx1WG/w2Q4STMMpjJP91qxOwAoNcL0=,tag:N8bKnO9N0ei06HDkSGt6XQ==,type:str] vaultwarden: environ: ENC[AES256_GCM,data:CST5I8x8qAkrTy/wbMLL6aFSPDPIU7aWsD1L1MnIATRmk7fcUhfTSFds7quJmIpb2znsIT/WxNI/V/7UW+9ZdPKI64hfPR8MtvrJcbOhU5Fe2IiytFymFbhcOgWAXjbGzs7knQmpfMxSl98sU71oLkRuFdkousdnh4VQFZhUCYM=,iv:Is6xQ7DGdcAQgrrXCS9NbJk67O2uR82rbKOXBTzZHWw=,tag:XVEjCEM5t8qJl6jL89zrkw==,type:str] +bluemap: + ssh-key: ENC[AES256_GCM,data:nPwsT4RYbMbGp2MChLUh6NXW4ckYr7SQcd6Gy2G8CEU+ugew5pt4d6GOK1fyekspDtet3EkPL2F1AsoPFBB2Rv0boARMslAhBqwWSsbBJTXeTEgAABSMxTPJRBtfJucvv426nyIj3uApoknz6mDCQh1OI6mER0fis7MPaM1506HlDlnIT0FV9EairEsaAmbd0yddByGJSccKIza2vW0qWqrz83P+xrakEONxFz0fJlkO5PRXCcQJVBCqWQfnaHNrWeBWv0QA7vAHlT0yjqJCpDRxN2KYrPWsz7sUbB4UZOtykCRM5kKFq73GUaOKqVECJQhcJi6tERhpJELwjjS8MSqvBD90UTKTshGugfuygTaOyUx4wou3atxMR2Rah9+uZ6mBrLAOLX3JKiAtyhFewPMWjd/UhbMPuzNageVBNz2EMpa4POSVwz5MyViKNSgr9cPcNGqmrnjvr/W/lnj6Ec+W80RiXQlADSE4Q6diLLwB9nlHvKs8NTDgv6sUafcPHpJ2+N4Jkb96dE14bMffQ385SI4vLDcQ8xCQ,iv:WdJIHRzjlm8bEldolCx1Q7pZJvjxGkNZALSOy3IjizU=,tag:5ZAikiqttq/76+thG+4LMw==,type:str] + ssh-known-hosts: ENC[AES256_GCM,data:J6V+NJ9TvYUL2gmcqWWYt8X+n0M7i0RpDpBelWAbFMH64+e9ztHNnC491sm+RogDxqKk0kwQyX2Mz00iq3Gc3wDYyozGOdv3tBKrp7/LcfjUQ9T9hi0yTD3eNV0LAjlAWMTdlW65VGHqGst8ncKbUuVxbBASVlh3A321toZgD+xxUAtNz7qKFa6fDbOS0xLD1+CmTwVp+aPos//QIKzjuk1HqxfBNK82maKtD4JHPS+Y3be2wIEjGWq3H6JYN/RDojD88D/jzo9RwvEjpqLXoOVfy8uX/fbEsgkgfAmPiaG+ePCnchSExEe3a6Y0E+I6YIzvP+tGThJpu4HaT/yW2Rww/jvsxKrXSUhtBZI/SIX5ZAIFB3sFjJXQefJjfNpQTQWhbspLfdemafGaRiDnzVgKDhNL1HNMNsXKDfWa0SLs4//dqerom/QCCNsaqV+4HVzv5x44srChGERadQI/Wh4UG2R19xxbdyIsKPHzv7BhEKufJkjc5upBjWygQrGAkTRHugFpw2Tdkz9yUQSujMkaeRKhVkA+ZUAjwnY5TwqNZBj7U3K2JXoNVHAq194XmrA2dNghh0OmRrvKGwM3HKexX22SXT0bPlpdWRQpMbUgV+uHLMerlDpNMFTIueEBkaF/FWeSW2N5WUrUb1uJ91QcJ8JBgN1riuD1Oxv9RRPrY9VVNJMrYjpAAREN8i8brMTOCJ35s7jnqIei0dNmnNXOoQZPs9kUMeEtUc/Df1E8/aO2Y4yU9gHUuevXnAJWFAiu2IxssgPk6CcNxvapJEmlwkLK/JyuDsWwFxVOHfw5QIEsoDVWXt6eMhquqUgzJI1q7QrTWUQsBb5A5sQKYWQHempOaXuQn1bzA7mU3Gzsr8bNNc6tpy+6j3zTXYR067EX00yqPG+kqRn4QVIuhByxXP3cwXLUG9uD1lsqWrGzs6WCnHr7txhRBXf4WbBVmXModO3uf36cDYEwrUa6yBsARtSl8PJ0UadfY/xULcT5PFvu9+Hi2qj3vp4IU3JCJa9AvXB+11pbSdawprjuDhwQtPwkJ4CQyvZsom3/BOrmwYM5+EyMDIluEQ0z6eDE5buiIVbX6IvXnDCKbrnqVwavX2wqyiDduFLjRfWL/3U2O1yRim78smrDMJABJZvtW+a+GfmlnTd/gnFvS70Fmm/lgtY051ISL/iFx6toJRoBMMiI/Zvy13uQry+w/HbyFl42DIank8tf7kuN3E9M7ADGMubRJJ0AZOcQddrFnR4Gl2nU2+3RS5fLHaBf9QHK6W92/n//xmPkYqrkPacew4eBjUqM32jVGuBpDc964fK9kdtIdw8q5P1s/ph3I79Y24kGeuO1AVJuZvkaTv1Z7GgI9+K9TstKJ9XpRCidLpLSP+uHOWkqcNsQlt6ilTlfHj+MKoD85dKZ315QMmpiuYEvzCSP1aYTb9dpd61Su/IVuM3r2NuINNEZ166YlHQVsLNpDn8E5ahk3ZInOAg6/kaKTmjUI8KEvX4BR3PbbViAlJJb3suJ0oZBGPUlrW5uLRmADvf2mMDVO5zY7/m9DQwxjt4Miu0l8ZaUc0YJQ850lBKucQ==,iv:GI8w7h7xX8gMHuAoWUyrW+BQb85LNlASoYvGBPlCZaI=,tag:WnHNMevfFSMc0ikBZwWn/g==,type:str] sops: kms: [] gcp_kms: [] @@ -92,8 +95,8 @@ sops: UHpLRkdQTnhkeGlWVG9VS1hkWktyckEKAdwnA9URLYZ50lMtXrU9Q09d0L3Zfsyr 4UsvjjdnFtsXwEZ9ZzOQrpiN0Oz24s3csw5KckDni6kslaloJZsLGg== -----END AGE ENCRYPTED FILE----- - lastmodified: "2024-08-26T19:38:58Z" - mac: ENC[AES256_GCM,data:3FyfZPmJ7znQEul+IwqN1ZaM53n6os3grquJwJ9vfyDSc2h8UZBhqYG+2uW9Znp9DSIjuhCUI8iqGKRJE0M/6IDICeXms/5+ynVFOS9bA2cdzPvWaj0FFAd2x3g4Vhs47+vRlsnIe/tMiKU3IOvzOfI6KAUHc9L2ySrzH7z2+fo=,iv:1iZSR9qOIEtf+fNbtWSwJBIUEQGKadfHSVOnkFzOwq8=,tag:Sk6JEU1B6Rd1GXLYC6rQtQ==,type:str] + lastmodified: "2024-09-01T01:33:50Z" + mac: ENC[AES256_GCM,data:PkcOD9hJWD5tILO9PuZkOgIoujt4q2qtHBB9KF8ikrNKo0yw24Jf1ceI5/+BHCxhdi8sF4qQM/zty61zqwNaBsvrsLUkdWDwUDsuJQa1KKZiCEZPqYBc+qGIQ5wNPsU2zJ0c8+wU8H0LtGqKOH9GmaQtTdm0Rt2IcexV823uTjQ=,iv:GYTI85OgqnN8iUc6OOXO7Sz2XIthWJtz8zwMuWutEYs=,tag:2rhfhjXXzZLzoVlkINo0ZQ==,type:str] pgp: - created_at: "2024-08-04T00:03:28Z" enc: |-