From 3e52c7648a7c7b8160b0787cfa44e3b12ce0a68b Mon Sep 17 00:00:00 2001 From: kat witch Date: Thu, 2 Sep 2021 05:19:04 +0100 Subject: [PATCH] hosts/daiyousei: init. auto hostnames. profiles/cross. --- config/hosts/athame/nixos.nix | 2 - config/hosts/beltane/nixos.nix | 1 - config/hosts/daiyousei/nixos.nix | 78 +++++++ config/hosts/ostara/nixos.nix | 1 - config/hosts/rinnosuke/nixos.nix | 28 ++- config/hosts/samhain/nixos.nix | 4 +- config/hosts/to-do/mabon/nixos/default.nix | 1 - config/hosts/yule/nixos.nix | 1 - config/modules/meta/network.nix | 7 +- config/modules/nixos/settings.nix | 3 - config/profiles/base/profiles.nix | 40 ++++ config/profiles/cross/aarch64.nix | 11 + config/profiles/cross/armv7.nix | 6 + config/profiles/cross/armvcommon.nix | 6 + config/profiles/cross/default.nix | 35 +++ config/profiles/hardware/oracle/common.nix | 231 ++++++++++++++++++++ config/profiles/hardware/oracle/default.nix | 26 +++ config/profiles/hardware/oracle/oracle.nix | 5 + config/profiles/hardware/oracle/ubuntu.nix | 5 + default.nix | 3 + nix/sources.json | 6 +- overlays/exprs | 2 +- 22 files changed, 479 insertions(+), 23 deletions(-) create mode 100644 config/hosts/daiyousei/nixos.nix create mode 100644 config/profiles/cross/aarch64.nix create mode 100644 config/profiles/cross/armv7.nix create mode 100644 config/profiles/cross/armvcommon.nix create mode 100644 config/profiles/cross/default.nix create mode 100644 config/profiles/hardware/oracle/common.nix create mode 100644 config/profiles/hardware/oracle/default.nix create mode 100644 config/profiles/hardware/oracle/oracle.nix create mode 100644 config/profiles/hardware/oracle/ubuntu.nix diff --git a/config/hosts/athame/nixos.nix b/config/hosts/athame/nixos.nix index 86a0080f..b4b7ae44 100644 --- a/config/hosts/athame/nixos.nix +++ b/config/hosts/athame/nixos.nix @@ -75,8 +75,6 @@ with lib; # Networking networking = { - hostName = "athame"; - domain = "kittywit.ch"; hostId = "7b0ac74e"; useDHCP = false; interfaces = { diff --git a/config/hosts/beltane/nixos.nix b/config/hosts/beltane/nixos.nix index c267eef8..f1e3d970 100644 --- a/config/hosts/beltane/nixos.nix +++ b/config/hosts/beltane/nixos.nix @@ -93,7 +93,6 @@ with lib; # Networking networking = { - hostName = "beltane"; hostId = "3ef9a419"; useDHCP = false; interfaces.eno1.ipv4.addresses = singleton { diff --git a/config/hosts/daiyousei/nixos.nix b/config/hosts/daiyousei/nixos.nix new file mode 100644 index 00000000..c05235bc --- /dev/null +++ b/config/hosts/daiyousei/nixos.nix @@ -0,0 +1,78 @@ +{ config, tf, meta, kw, pkgs, lib, sources, ... }: with lib; let + oci-root = meta.deploy.targets.oci-root.tf; + addr_ipv6_nix = + let + prefix = head (splitString "/" (oci-root.resources.oci_kw_subnet.importAttr "ipv6cidr_block")); + in + assert hasSuffix "::" prefix; prefix + toString config.kw.oci.network.publicV6; +in +{ + imports = with meta; [ + profiles.hardware.oracle.ubuntu + services.nginx + ]; + + deploy.tf.providers.local = {}; + + nixpkgs.localSystem = systems.examples.aarch64-multiplatform // { + system = "aarch64-linux"; + }; + + kw.oci = { + enable = true; + base = "Canonical Ubuntu"; + specs = { + shape = "VM.Standard.A1.Flex"; + cores = 4; + ram = 24; + space = 100; + }; + ad = 1; + network = { + publicV6 = 6; + privateV4 = 5; + }; + }; + + services.nginx.virtualHosts = + let + splashy = pkgs.host-splash-site config.networking.hostName; + in + kw.virtualHostGen { + networkFilter = [ "public" ]; + block.locations."/" = { root = splashy; }; + }; + + networking = { + useDHCP = false; + interfaces.ens3 = { + useDHCP = true; + ipv6 = { + addresses = mkIf (config.network.addresses.public.nixos.ipv6.enable) [{ + address = config.network.addresses.public.nixos.ipv6.address; + prefixLength = 64; + }]; + routes = [{ + address = "::"; + prefixLength = 0; + }]; + }; + }; + }; + + network = { + addresses = { + public = { + enable = true; + # TODO: move into module + nixos.ipv6.address = mkIf (tf.state.resources ? ${tf.resources.${config.networking.hostName}.out.reference}) addr_ipv6_nix; + tf.ipv6.address = tf.resources."${config.networking.hostName}_ipv6".refAttr "ip_address"; + }; + }; + firewall.public.interfaces = singleton "ens3"; + tf = { + enable = true; + ipv4_attr = "public_ip"; + }; + }; +} diff --git a/config/hosts/ostara/nixos.nix b/config/hosts/ostara/nixos.nix index 3ba48a72..dd578167 100644 --- a/config/hosts/ostara/nixos.nix +++ b/config/hosts/ostara/nixos.nix @@ -49,7 +49,6 @@ with lib; # Networking networking = { - hostName = "ostara"; hostId = "9f89b327"; useDHCP = false; interfaces.enp1s0.ipv4.addresses = singleton { diff --git a/config/hosts/rinnosuke/nixos.nix b/config/hosts/rinnosuke/nixos.nix index 924feb6b..8b78637a 100644 --- a/config/hosts/rinnosuke/nixos.nix +++ b/config/hosts/rinnosuke/nixos.nix @@ -4,17 +4,30 @@ let prefix = head (splitString "/" (oci-root.resources.oci_kw_subnet.importAttr "ipv6cidr_block")); in - assert hasSuffix "::" prefix; prefix + "7"; + assert hasSuffix "::" prefix; prefix + toString config.kw.oci.network.publicV6; in { - imports = (with (import (sources.tf-nix + "/modules")); [ - nixos.ubuntu-linux - nixos.oracle - ./oracle.nix - ]) ++ (with meta; [ + imports = with meta; [ + profiles.hardware.oracle.ubuntu services.knot services.nginx - ]); + ]; + + kw.oci = { + enable = true; + base = "Canonical Ubuntu"; + specs = { + shape = "VM.Standard.E2.1.Micro"; + cores = 1; + ram = 1; + space = 50; + }; + ad = 2; + network = { + publicV6 = 7; + privateV4 = 3; + }; + }; services.nginx.virtualHosts = let @@ -27,7 +40,6 @@ in networking = { useDHCP = false; - hostName = "rinnosuke"; interfaces.ens3 = { useDHCP = true; ipv6 = { diff --git a/config/hosts/samhain/nixos.nix b/config/hosts/samhain/nixos.nix index 313a00d1..243605e9 100644 --- a/config/hosts/samhain/nixos.nix +++ b/config/hosts/samhain/nixos.nix @@ -14,6 +14,9 @@ in profiles.hardware.ms-7b86 profiles.gui profiles.vfio + profiles.cross.aarch64 + profiles.cross.armv7l + profiles.cross.armv6l users.kat.guiFull users.kat.services.weechat services.nginx @@ -178,7 +181,6 @@ in # Networking networking = { - hostName = "samhain"; hostId = "617050fc"; useDHCP = false; useNetworkd = true; diff --git a/config/hosts/to-do/mabon/nixos/default.nix b/config/hosts/to-do/mabon/nixos/default.nix index 1495ba65..071a7ea0 100644 --- a/config/hosts/to-do/mabon/nixos/default.nix +++ b/config/hosts/to-do/mabon/nixos/default.nix @@ -19,7 +19,6 @@ boot.extraModulePackages = [ config.boot.kernelPackages.broadcom_sta ]; networking.hostId = "d199ad70"; - networking.hostName = "mabon"; networking.useDHCP = false; networking.interfaces.enp1s0.useDHCP = false; diff --git a/config/hosts/yule/nixos.nix b/config/hosts/yule/nixos.nix index 02b56a25..6b6d2c12 100644 --- a/config/hosts/yule/nixos.nix +++ b/config/hosts/yule/nixos.nix @@ -63,7 +63,6 @@ with lib; networking = { hostId = "dddbb888"; - hostName = "yule"; useDHCP = false; wireless.interfaces = singleton "wlp2s0"; interfaces = { diff --git a/config/modules/meta/network.nix b/config/modules/meta/network.nix index b1b1358b..bbbaeb42 100644 --- a/config/modules/meta/network.nix +++ b/config/modules/meta/network.nix @@ -31,7 +31,12 @@ with lib; config = { nixpkgs = { system = mkDefault pkgs.system; - pkgs = mkDefault pkgs; + pkgs = let + pkgsReval = import pkgs.path { + inherit (config.nixpkgs) localSystem crossSystem; + inherit (pkgs) overlays config; + }; + in mkDefault (if config.nixpkgs.config == pkgs.config && config.nixpkgs.localSystem.system == pkgs.targetPlatform.system then pkgs else pkgsReval); }; }; }; diff --git a/config/modules/nixos/settings.nix b/config/modules/nixos/settings.nix index eba56e59..3c5e2105 100644 --- a/config/modules/nixos/settings.nix +++ b/config/modules/nixos/settings.nix @@ -7,7 +7,4 @@ (sources.tf-nix + "/modules/nixos/secrets.nix") (sources.tf-nix + "/modules/nixos/secrets-users.nix") ]; - excludes = [ - "oracle" - ]; } diff --git a/config/profiles/base/profiles.nix b/config/profiles/base/profiles.nix index be8b1b22..0b55c3d9 100644 --- a/config/profiles/base/profiles.nix +++ b/config/profiles/base/profiles.nix @@ -8,6 +8,21 @@ with lib; gui = mkEnableOption "Graphical System"; vfio = mkEnableOption "VFIO"; trusted = mkEnableOption "Trusted Submodule"; + cross = { + enable = mkEnableOption "cross/emulated compilation"; + aarch64 = mkOption { + type = types.bool; + default = false; + }; + armv6l = mkOption { + type = types.bool; + default = false; + }; + armv7l = mkOption { + type = types.bool; + default = false; + }; + }; hardware = { acs-override = mkEnableOption "ACS IOMMU Override"; amdgpu = mkEnableOption "AMD GPU"; @@ -18,6 +33,11 @@ with lib; ryzen = mkEnableOption "AMD Ryzen CPU"; ms-7b86 = mkEnableOption "MSI B450-A Pro Max"; rm-310 = mkEnableOption "Intel DQ67OW"; + oracle = { + common = mkEnableOption "OCI"; + ubuntu = mkEnableOption "Canonical Ubuntu Base Image"; + oracle = mkEnableOption "Oracle Linux Base Image"; + }; eeepc-1015pem = mkEnableOption "Asus Eee PC 1015PEM"; v330-14arr = mkEnableOption "Lenovo Ideapad v330-14ARR"; }; @@ -30,6 +50,21 @@ with lib; gui = mkEnableOption "Graphical System"; vfio = mkEnableOption "VFIO"; trusted = mkEnableOption "Trusted Submodule"; + cross = { + enable = mkEnableOption "cross/emulated compilation"; + aarch64 = mkOption { + type = types.bool; + default = false; + }; + armv6l = mkOption { + type = types.bool; + default = false; + }; + armv7l = mkOption { + type = types.bool; + default = false; + }; + }; hardware = { acs-override = mkEnableOption "ACS IOMMU Override"; amdgpu = mkEnableOption "AMD GPU"; @@ -40,6 +75,11 @@ with lib; ryzen = mkEnableOption "AMD Ryzen CPU"; ms-7b86 = mkEnableOption "MSI B450-A Pro Max"; rm-310 = mkEnableOption "Intel DQ67OW"; + oracle = { + common = mkEnableOption "OCI"; + ubuntu = mkEnableOption "Canonical Ubuntu Base Image"; + oracle = mkEnableOption "Oracle Linux Base Image"; + }; eeepc-1015pem = mkEnableOption "Asus Eee PC 1015PEM"; v330-14arr = mkEnableOption "Lenovo Ideapad v330-14ARR"; }; diff --git a/config/profiles/cross/aarch64.nix b/config/profiles/cross/aarch64.nix new file mode 100644 index 00000000..b9b9c0c0 --- /dev/null +++ b/config/profiles/cross/aarch64.nix @@ -0,0 +1,11 @@ + +{ pkgs, config, lib, ... }: with lib; { + boot.binfmt = { + emulatedSystems = [ "aarch64-linux" ]; + /* + registrations.aarch64-linux = { + interpreter = mkForce "${pkgs.qemu-vfio or pkgs.qemu}/bin/qemu-aarch64"; + }; + */ + }; +} diff --git a/config/profiles/cross/armv7.nix b/config/profiles/cross/armv7.nix new file mode 100644 index 00000000..0cd9653c --- /dev/null +++ b/config/profiles/cross/armv7.nix @@ -0,0 +1,6 @@ +{ config, ... }: { + nix = { + binaryCaches = [ "https://arm.cachix.org/" ]; + binaryCachePublicKeys = [ "arm.cachix.org-1:5BZ2kjoL1q6nWhlnrbAl+G7ThY7+HaBRD9PZzqZkbnM=" ]; + }; +} diff --git a/config/profiles/cross/armvcommon.nix b/config/profiles/cross/armvcommon.nix new file mode 100644 index 00000000..22fe7a6f --- /dev/null +++ b/config/profiles/cross/armvcommon.nix @@ -0,0 +1,6 @@ +{ config, ... }: { + nix = { + binaryCaches = [ "https://thefloweringash-armv7.cachix.org/" ]; + binaryCachePublicKeys = [ "thefloweringash-armv7.cachix.org-1:v+5yzBD2odFKeXbmC+OPWVqx4WVoIVO6UXgnSAWFtso=" ]; + }; +} diff --git a/config/profiles/cross/default.nix b/config/profiles/cross/default.nix new file mode 100644 index 00000000..2b8f7841 --- /dev/null +++ b/config/profiles/cross/default.nix @@ -0,0 +1,35 @@ +rec { + common = ./armvcommon.nix; + armv7-base = ./armv7.nix; + aarch64-base = ./aarch64.nix; + + + aarch64 = { + deploy.profile.cross = { + enable = true; + aarch64 = true; + }; + imports = [ + aarch64-base + ]; + }; + armv7l = { + deploy.profile.cross = { + enable = true; + armv7l = true; + }; + imports = [ + common + armv7-base + ]; + }; + armv6l = { + deploy.profile.cross = { + enable = true; + armv6l = true; + }; + imports = [ + common + ]; + }; +} diff --git a/config/profiles/hardware/oracle/common.nix b/config/profiles/hardware/oracle/common.nix new file mode 100644 index 00000000..e6703f59 --- /dev/null +++ b/config/profiles/hardware/oracle/common.nix @@ -0,0 +1,231 @@ +{ config, tf, meta, kw, pkgs, lib, sources, ... }: with lib; let + oci-root = meta.deploy.targets.oci-root.tf; + cfg = config.kw.oci; +in +{ + options.kw.oci = { + enable = mkEnableOption "oracle server"; + base = mkOption { + description = '' + Canonical Ubuntu provides an EXT4 root filesystem. + Oracle Linux provides an XFS root filesystem. + ''; + type = with types; enum [ + "Canonical Ubuntu" + "Oracle Linux" + ]; + default = "Canonical Ubuntu"; + }; + specs = { + shape = mkOption { + type = with types; nullOr str; + default = null; + }; + cores = mkOption { + type = with types; nullOr int; + default = null; + }; + ram = mkOption { + type = with types; nullOr int; + default = null; + }; + space = mkOption { + type = with types; nullOr int; + default = null; + }; + }; + network = { + privateV4 = mkOption { + type = with types; nullOr int; + default = null; + }; + publicV6 = mkOption { + type = with types; nullOr int; + default = null; + }; + }; + ad = mkOption { + description = '' + Availability Domain. + Important because, for example: EPYC instances can only be provisioned on AD2 in London. + ''; + type = with types; nullOr int; + default = null; + }; + }; + imports = with import (sources.tf-nix + "/modules"); [ + nixos.oracle + ]; + config = { + deploy.tf = + let + compartment_id = oci-root.resources.oci_kw_compartment.importAttr "id"; + inherit (tf.lib.tf) terraformExpr; + in + { + deploy.systems."${config.networking.hostName}" = { + lustrate = { + enable = true; + connection = tf.resources."${config.networking.hostName}".connection.set; + }; + connection = { + port = head config.services.openssh.ports; + }; + }; + providers.oci = { + inputs = { + tenancy_ocid = oci-root.outputs.oci_tenancy.import; + user_ocid = oci-root.resources.oci_kw_user.importAttr "id"; + fingerprint = oci-root.resources.oci_kw_apikey.importAttr "fingerprint"; + region = oci-root.outputs.oci_region.import; + private_key_path = oci-root.resources.oci_kw_key_file.importAttr "filename"; + }; + }; + resources = mkMerge [{ + cloudinit = { + provider = "cloudinit"; + type = "config"; + dataSource = true; + inputs = { + part = singleton { + content_type = "text/cloud-config"; + content = "#cloud-config\n" + builtins.toJSON { + disable_root = false; + }; + }; + }; + }; + availability_domain = { + provider = "oci"; + type = "identity_availability_domain"; + dataSource = true; + inputs = { + inherit compartment_id; + ad_number = cfg.ad; + }; + }; + generic_image = { + provider = "oci"; + type = "core_images"; + dataSource = true; + inputs = { + inherit compartment_id; + inherit (tf.resources."${config.networking.hostName}".inputs) shape; + operating_system = cfg.base; + sort_by = "TIMECREATED"; + sort_order = "DESC"; + }; + }; + "${config.networking.hostName}_vnic" = { + provider = "oci"; + type = "core_vnic_attachments"; + dataSource = true; + inputs = { + inherit compartment_id; + instance_id = tf.resources."${config.networking.hostName}".refAttr "id"; + }; + }; + "${config.networking.hostName}_ipv6" = { + provider = "oci"; + type = "core_ipv6"; + inputs = { + vnic_id = tf.resources."${config.networking.hostName}_vnic".refAttr "vnic_attachments[0].vnic_id"; + display_name = config.networking.hostName; + ip_address = terraformExpr ''cidrhost("${oci-root.resources.oci_kw_subnet.importAttr "ipv6cidr_block"}", ${toString cfg.network.publicV6})''; + }; + }; + "${config.networking.hostName}" = { + provider = "oci"; + type = "core_instance"; + inputs = { + inherit compartment_id; + extended_metadata = { }; + metadata = { + ssh_authorized_keys = concatStringsSep "\n" config.users.users.root.openssh.authorizedKeys.keys; + user_data = tf.resources.cloudinit.refAttr "rendered"; + }; + shape = cfg.specs.shape; + shape_config = { + ocpus = cfg.specs.cores; + memory_in_gbs = cfg.specs.ram; + }; + source_details = { + source_type = "image"; + source_id = tf.resources.generic_image.refAttr "images[0].id"; + boot_volume_size_in_gbs = cfg.specs.space; # min 50GB, up to 200GB free + }; + create_vnic_details = [ + { + assign_public_ip = true; + subnet_id = oci-root.resources.oci_kw_subnet.importAttr "id"; + private_ip = terraformExpr ''cidrhost("${oci-root.resources.oci_kw_subnet.importAttr "cidr_block"}", ${toString cfg.network.privateV4})''; + nsg_ids = [ + (tf.resources.firewall_group.refAttr "id") + ]; + } + ]; + availability_domain = tf.resources.availability_domain.refAttr "name"; + }; + lifecycle.ignoreChanges = [ + "source_details[0].source_id" + ]; + connection = { + type = "ssh"; + user = "root"; + host = tf.lib.tf.terraformSelf "public_ip"; + timeout = "5m"; + }; + }; + firewall_group = { + provider = "oci"; + type = "core_network_security_group"; + inputs = { + display_name = "${config.networking.hostName} firewall group"; + inherit compartment_id; + vcn_id = oci-root.resources.oci_vcn.importAttr "id"; + }; + }; + } + ( + let + protoValues = { + TCP = 6; + UDP = 17; + }; + inherit (config.networking) firewall; + ipv4 = "0.0.0.0/0"; + ipv6 = "::/0"; + mapPort = source: protocol: port: { + provider = "oci"; + type = "core_network_security_group_security_rule"; + inputs = { + network_security_group_id = tf.resources.firewall_group.refAttr "id"; + inherit protocol source; + direction = "INGRESS"; + ${if protocol == protoValues.TCP then "tcp_options" else "udp_options"} = { + destination_port_range = + if isAttrs port then { + min = port.from; + max = port.to; + } else { + min = port; + max = port; + }; + }; + }; + }; + mapAll = protocol: port: [ (mapPort ipv4 protocol port) (mapPort ipv6 protocol port) ]; + mapAllForInterface = + let + protos = [ "TCP" "UDP" ]; + types = [ "Ports" "PortRanges" ]; + in + interface: concatMap (type: concatMap (proto: (concatMap (port: (mapAll protoValues.${proto}) port) interface."allowed${proto}${type}")) protos) types; + rules = concatMap mapAllForInterface ([ firewall ] ++ map (interface: firewall.interfaces.${interface}) config.network.firewall.public.interfaces); + # TODO: use `count` and index into a fancy json or something? + in + listToAttrs (imap0 (i: rule: nameValuePair "firewall${toString i}" rule) rules) + )]; + }; + }; + } diff --git a/config/profiles/hardware/oracle/default.nix b/config/profiles/hardware/oracle/default.nix new file mode 100644 index 00000000..925b7687 --- /dev/null +++ b/config/profiles/hardware/oracle/default.nix @@ -0,0 +1,26 @@ +rec { + common = ./common.nix; + ubuntu-base = ./ubuntu.nix; + oracle-base = ./oracle.nix; + + ubuntu = { + deploy.profile.hardware.oracle = { + common = true; + ubuntu = true; + }; + imports = [ + common + ubuntu-base + ]; + }; + oracle = { + deploy.profile.hardware.oracle = { + common = true; + oracle = true; + }; + imports = [ + common + oracle-base + ]; + }; +} diff --git a/config/profiles/hardware/oracle/oracle.nix b/config/profiles/hardware/oracle/oracle.nix new file mode 100644 index 00000000..c13ddd68 --- /dev/null +++ b/config/profiles/hardware/oracle/oracle.nix @@ -0,0 +1,5 @@ +{ config, sources, ... }: { + imports = with import (sources.tf-nix + "/modules"); [ + nixos.oracle-linux + ]; +} diff --git a/config/profiles/hardware/oracle/ubuntu.nix b/config/profiles/hardware/oracle/ubuntu.nix new file mode 100644 index 00000000..5e8a6285 --- /dev/null +++ b/config/profiles/hardware/oracle/ubuntu.nix @@ -0,0 +1,5 @@ +{ sources, ... }: { + imports = with import (sources.tf-nix + "/modules"); [ + nixos.ubuntu-linux + ]; +} diff --git a/default.nix b/default.nix index d8205303..6aa29b49 100644 --- a/default.nix +++ b/default.nix @@ -69,6 +69,9 @@ let (host: { network.nodes.${host} = { imports = config.lib.kw.nodeImport host; + networking = { + hostName = host; + }; }; }) (lib.attrNames xarg.hosts)); diff --git a/nix/sources.json b/nix/sources.json index df186730..de77738a 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -89,10 +89,10 @@ "homepage": null, "owner": "kittywitch", "repo": "nixexprs", - "rev": "1ba4276349007ac90a9bda6e856752945a2ba342", - "sha256": "0m6v8aykjzvb5phr9kwazkyjp3bbzv6wys6jzvkpqgysl8mxyfhf", + "rev": "fb4a435201384c6a45b593d47bc72b4998ffe78d", + "sha256": "07r2n6xx6l4pbrqdda42amxdq9d9nqg8pnkqbimfhssakpnkv2ml", "type": "tarball", - "url": "https://github.com/kittywitch/nixexprs/archive/1ba4276349007ac90a9bda6e856752945a2ba342.tar.gz", + "url": "https://github.com/kittywitch/nixexprs/archive/fb4a435201384c6a45b593d47bc72b4998ffe78d.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "niv": { diff --git a/overlays/exprs b/overlays/exprs index 1ba42763..fb4a4352 160000 --- a/overlays/exprs +++ b/overlays/exprs @@ -1 +1 @@ -Subproject commit 1ba4276349007ac90a9bda6e856752945a2ba342 +Subproject commit fb4a435201384c6a45b593d47bc72b4998ffe78d