diff options
author | github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> | 2022-02-16 00:02:21 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-16 00:02:21 +0000 |
commit | 7b0b3b62252f6b072325e2b797bd13c647e02cd3 (patch) | |
tree | c7a87e3b7fe1eda6cc4437b09736164cdba80e76 /nixos | |
parent | dab1c23e40877a3a11ee660df8ea2c605680d87b (diff) | |
parent | a8291e984a341e85d167978f0ae32d1dcaa98568 (diff) | |
download | nixpkgs-7b0b3b62252f6b072325e2b797bd13c647e02cd3.tar nixpkgs-7b0b3b62252f6b072325e2b797bd13c647e02cd3.tar.gz nixpkgs-7b0b3b62252f6b072325e2b797bd13c647e02cd3.tar.bz2 nixpkgs-7b0b3b62252f6b072325e2b797bd13c647e02cd3.tar.lz nixpkgs-7b0b3b62252f6b072325e2b797bd13c647e02cd3.tar.xz nixpkgs-7b0b3b62252f6b072325e2b797bd13c647e02cd3.tar.zst nixpkgs-7b0b3b62252f6b072325e2b797bd13c647e02cd3.zip |
Merge staging-next into staging
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/doc/manual/from_md/release-notes/rl-2205.section.xml | 25 | ||||
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2205.section.md | 11 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 3 | ||||
-rw-r--r-- | nixos/modules/services/home-automation/home-assistant.nix (renamed from nixos/modules/services/misc/home-assistant.nix) | 350 | ||||
-rw-r--r-- | nixos/modules/services/web-servers/agate.nix | 148 | ||||
-rw-r--r-- | nixos/modules/system/activation/switch-to-configuration.pl | 9 | ||||
-rw-r--r-- | nixos/modules/system/activation/top-level.nix | 2 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 1 | ||||
-rw-r--r-- | nixos/tests/home-assistant.nix | 102 | ||||
-rw-r--r-- | nixos/tests/web-servers/agate.nix | 29 |
10 files changed, 557 insertions, 123 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index 4e64a02de81..c234cda499f 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -162,6 +162,14 @@ </listitem> <listitem> <para> + <link xlink:href="https://github.com/mbrubeck/agate">agate</link>, + a very simple server for the Gemini hypertext protocol. + Available as + <link xlink:href="options.html#opt-services.agate.enable">services.agate</link>. + </para> + </listitem> + <listitem> + <para> <link xlink:href="https://github.com/JustArchiNET/ArchiSteamFarm">ArchiSteamFarm</link>, a C# application with primary purpose of idling Steam cards from multiple accounts simultaneously. Available as @@ -271,6 +279,23 @@ </listitem> <listitem> <para> + The <literal>home-assistant</literal> module now requires + users that don’t want their configuration to be managed + declaratively to set + <literal>services.home-assistant.config = null;</literal>. + This is required due to the way default settings are handled + with the new settings style. + </para> + <para> + Additionally the default list of + <literal>extraComponents</literal> now includes the minimal + dependencies to successfully complete the + <link xlink:href="https://www.home-assistant.io/getting-started/onboarding/">onboarding</link> + procedure. + </para> + </listitem> + <listitem> + <para> <literal>pkgs.emacsPackages.orgPackages</literal> is removed because org elpa is deprecated. The packages in the top level of <literal>pkgs.emacsPackages</literal>, such as org and diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index 10349f96d4a..567a6d6780a 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -49,6 +49,8 @@ In addition to numerous new and upgraded packages, this release has the followin - [tetrd](https://tetrd.app), share your internet connection from your device to your PC and vice versa through a USB cable. Available at [services.tetrd](#opt-services.tetrd.enable). +- [agate](https://github.com/mbrubeck/agate), a very simple server for the Gemini hypertext protocol. Available as [services.agate](options.html#opt-services.agate.enable). + - [ArchiSteamFarm](https://github.com/JustArchiNET/ArchiSteamFarm), a C# application with primary purpose of idling Steam cards from multiple accounts simultaneously. Available as [services.archisteamfarm](options.html#opt-services.archisteamfarm.enable). - [teleport](https://goteleport.com), allows engineers and security professionals to unify access for SSH servers, Kubernetes clusters, web applications, and databases across all environments. Available at [services.teleport](#opt-services.teleport.enable). @@ -91,6 +93,15 @@ In addition to numerous new and upgraded packages, this release has the followin `useLLVM`. So instead of `(ghc.withPackages (p: [])).override { withLLVM = true; }`, one needs to use `(ghc.withPackages.override { useLLVM = true; }) (p: [])`. +- The `home-assistant` module now requires users that don't want their + configuration to be managed declaratively to set + `services.home-assistant.config = null;`. This is required + due to the way default settings are handled with the new settings style. + + Additionally the default list of `extraComponents` now includes the minimal + dependencies to successfully complete the [onboarding](https://www.home-assistant.io/getting-started/onboarding/) + procedure. + - `pkgs.emacsPackages.orgPackages` is removed because org elpa is deprecated. The packages in the top level of `pkgs.emacsPackages`, such as org and org-contrib, refer to the ones in `pkgs.emacsPackages.elpaPackages` and diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index fa44d01b5aa..b6d9bd00629 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -450,6 +450,7 @@ ./services/hardware/undervolt.nix ./services/hardware/vdr.nix ./services/hardware/xow.nix + ./services/home-automation/home-assistant.nix ./services/logging/SystemdJournal2Gelf.nix ./services/logging/awstats.nix ./services/logging/filebeat.nix @@ -545,7 +546,6 @@ ./services/misc/headphones.nix ./services/misc/heisenbridge.nix ./services/misc/greenclip.nix - ./services/misc/home-assistant.nix ./services/misc/ihaskell.nix ./services/misc/input-remapper.nix ./services/misc/irkerd.nix @@ -1057,6 +1057,7 @@ ./services/web-apps/wordpress.nix ./services/web-apps/youtrack.nix ./services/web-apps/zabbix.nix + ./services/web-servers/agate.nix ./services/web-servers/apache-httpd/default.nix ./services/web-servers/caddy/default.nix ./services/web-servers/darkhttpd.nix diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix index fc8ce08b2e1..bdd5e82bd26 100644 --- a/nixos/modules/services/misc/home-assistant.nix +++ b/nixos/modules/services/home-automation/home-assistant.nix @@ -4,35 +4,27 @@ with lib; let cfg = config.services.home-assistant; + format = pkgs.formats.yaml {}; - # cfg.config != null can be assumed here - configJSON = pkgs.writeText "configuration.json" - (builtins.toJSON (if cfg.applyDefaultConfig then - (recursiveUpdate defaultConfig cfg.config) else cfg.config)); + # Render config attribute sets to YAML + # Values that are null will be filtered from the output, so this is one way to have optional + # options shown in settings. + # We post-process the result to add support for YAML functions, like secrets or includes, see e.g. + # https://www.home-assistant.io/docs/configuration/secrets/ + filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) cfg.config or {}; configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } '' - ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out - # Hack to support custom yaml objects, - # i.e. secrets: https://www.home-assistant.io/docs/configuration/secrets/ + cp ${format.generate "configuration.yaml" filteredConfig} $out sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out ''; + lovelaceConfig = cfg.lovelaceConfig or {}; + lovelaceConfigFile = format.generate "ui-lovelace.yaml" lovelaceConfig; - lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json" - (builtins.toJSON cfg.lovelaceConfig); - lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { preferLocalBuild = true; } '' - ${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out - ''; - + # Components advertised by the home-assistant package availableComponents = cfg.package.availableComponents; + # Components that were added by overriding the package explicitComponents = cfg.package.extraComponents; - - usedPlatforms = config: - if isAttrs config then - optional (config ? platform) config.platform - ++ concatMap usedPlatforms (attrValues config) - else if isList config then - concatMap usedPlatforms config - else [ ]; + useExplicitComponent = component: elem component explicitComponents; # Given a component "platform", looks up whether it is used in the config # as `platform = "platform";`. @@ -42,34 +34,46 @@ let # platform = "mqtt"; # ... # } ]; - useComponentPlatform = component: elem component (usedPlatforms cfg.config); + usedPlatforms = config: + if isAttrs config then + optional (config ? platform) config.platform + ++ concatMap usedPlatforms (attrValues config) + else if isList config then + concatMap usedPlatforms config + else [ ]; - useExplicitComponent = component: elem component explicitComponents; + useComponentPlatform = component: elem component (usedPlatforms cfg.config); - # Returns whether component is used in config or explicitly passed into package + # Returns whether component is used in config, explicitly passed into package or + # configured in the module. useComponent = component: hasAttrByPath (splitString "." component) cfg.config || useComponentPlatform component - || useExplicitComponent component; + || useExplicitComponent component + || builtins.elem component cfg.extraComponents; - # List of components used in config + # Final list of components passed into the package to include required dependencies extraComponents = filter useComponent availableComponents; - package = if (cfg.autoExtraComponents && cfg.config != null) - then (cfg.package.override { inherit extraComponents; }) - else cfg.package; + package = (cfg.package.override (oldArgs: { + # Respect overrides that already exist in the passed package and + # concat it with values passed via the module. + extraComponents = oldArgs.extraComponents ++ extraComponents; + extraPackages = ps: (oldArgs.extraPackages ps) ++ (cfg.extraPackages ps); + })); +in { + imports = [ + # Migrations in NixOS 22.05 + (mkRemovedOptionModule [ "services" "home-assistant" "applyDefaultConfig" ] "The default config was migrated into services.home-assistant.config") + (mkRemovedOptionModule [ "services" "home-assistant" "autoExtraComponents" ] "Components are now parsed from services.home-assistant.config unconditionally") + (mkRenamedOptionModule [ "services" "home-assistant" "port" ] [ "services" "home-assistant" "config" "http" "server_port" ]) + ]; - # If you are changing this, please update the description in applyDefaultConfig - defaultConfig = { - homeassistant.time_zone = config.time.timeZone; - http.server_port = cfg.port; - } // optionalAttrs (cfg.lovelaceConfig != null) { - lovelace.mode = "yaml"; + meta = { + buildDocsInSandbox = false; + maintainers = teams.home-assistant.members; }; -in { - meta.maintainers = teams.home-assistant.members; - options.services.home-assistant = { # Running home-assistant on NixOS is considered an installation method that is unsupported by the upstream project. # https://github.com/home-assistant/architecture/blob/master/adr/0012-define-supported-installation-method.md#decision @@ -81,42 +85,166 @@ in { description = "The config directory, where your <filename>configuration.yaml</filename> is located."; }; - port = mkOption { - default = 8123; - type = types.port; - description = "The port on which to listen."; + extraComponents = mkOption { + type = types.listOf (types.enum availableComponents); + default = [ + # List of components required to complete the onboarding + "default_config" + "met" + "esphome" + ] ++ optionals (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) [ + # Use the platform as an indicator that we might be running on a RaspberryPi and include + # relevant components + "rpi_power" + ]; + example = literalExpression '' + [ + "analytics" + "default_config" + "esphome" + "my" + "shopping_list" + "wled" + ] + ''; + description = '' + List of <link xlink:href="https://www.home-assistant.io/integrations/">components</link> that have their dependencies included in the package. + + The component name can be found in the URL, for example <literal>https://www.home-assistant.io/integrations/ffmpeg/</literal> would map to <literal>ffmpeg</literal>. + ''; }; - applyDefaultConfig = mkOption { - default = true; - type = types.bool; + extraPackages = mkOption { + type = types.functionTo (types.listOf types.package); + default = _: []; + defaultText = literalExpression '' + python3Packages: with python3Packages; []; + ''; + example = literalExpression '' + python3Packages: with python3Packages; [ + # postgresql support + psycopg2 + ]; + ''; description = '' - Setting this option enables a few configuration options for HA based on NixOS configuration (such as time zone) to avoid having to manually specify configuration we already have. - </para> - <para> - Currently one side effect of enabling this is that the <literal>http</literal> component will be enabled. - </para> - <para> - This only takes effect if <literal>config != null</literal> in order to ensure that a manually managed <filename>configuration.yaml</filename> is not overwritten. + List of packages to add to propagatedBuildInputs. + + A popular example is <package>python3Packages.psycopg2</package> + for PostgreSQL support in the recorder component. ''; }; config = mkOption { - default = null; - # Migrate to new option types later: https://github.com/NixOS/nixpkgs/pull/75584 - type = with lib.types; let - valueType = nullOr (oneOf [ - bool - int - float - str - (lazyAttrsOf valueType) - (listOf valueType) - ]) // { - description = "Yaml value"; - emptyValue.value = {}; + type = types.submodule { + freeformType = format.type; + options = { + # This is a partial selection of the most common options, so new users can quickly + # pick up how to match home-assistants config structure to ours. It also lets us preset + # config values intelligently. + + homeassistant = { + # https://www.home-assistant.io/docs/configuration/basic/ + name = mkOption { + type = types.nullOr types.str; + default = null; + example = "Home"; + description = '' + Name of the location where Home Assistant is running. + ''; + }; + + latitude = mkOption { + type = types.nullOr (types.either types.float types.str); + default = null; + example = 52.3; + description = '' + Latitude of your location required to calculate the time the sun rises and sets. + ''; + }; + + longitude = mkOption { + type = types.nullOr (types.either types.float types.str); + default = null; + example = 4.9; + description = '' + Longitude of your location required to calculate the time the sun rises and sets. + ''; + }; + + unit_system = mkOption { + type = types.nullOr (types.enum [ "metric" "imperial" ]); + default = null; + example = "metric"; + description = '' + The unit system to use. This also sets temperature_unit, Celsius for Metric and Fahrenheit for Imperial. + ''; + }; + + temperature_unit = mkOption { + type = types.nullOr (types.enum [ "C" "F" ]); + default = null; + example = "C"; + description = '' + Override temperature unit set by unit_system. <literal>C</literal> for Celsius, <literal>F</literal> for Fahrenheit. + ''; + }; + + time_zone = mkOption { + type = types.nullOr types.str; + default = config.time.timeZone or null; + defaultText = literalExpression '' + config.time.timeZone or null + ''; + example = "Europe/Amsterdam"; + description = '' + Pick your time zone from the column TZ of Wikipedia’s <link xlink:href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">list of tz database time zones</link>. + ''; + }; + }; + + http = { + # https://www.home-assistant.io/integrations/http/ + server_host = mkOption { + type = types.either types.str (types.listOf types.str); + default = [ + "0.0.0.0" + "::" + ]; + example = "::1"; + description = '' + Only listen to incoming requests on specific IP/host. The default listed assumes support for IPv4 and IPv6. + ''; + }; + + server_port = mkOption { + default = 8123; + type = types.port; + description = '' + The port on which to listen. + ''; + }; }; - in valueType; + + lovelace = { + # https://www.home-assistant.io/lovelace/dashboards/ + mode = mkOption { + type = types.enum [ "yaml" "storage" ]; + default = if cfg.lovelaceConfig != null + then "yaml" + else "storage"; + defaultText = literalExpression '' + if cfg.lovelaceConfig != null + then "yaml" + else "storage"; + ''; + example = "yaml"; + description = '' + In what mode should the main Lovelace panel be, <literal>yaml</literal> or <literal>storage</literal> (UI managed). + ''; + }; + }; + }; + }; example = literalExpression '' { homeassistant = { @@ -130,15 +258,19 @@ in { frontend = { themes = "!include_dir_merge_named themes"; }; - http = { }; + http = {}; feedreader.urls = [ "https://nixos.org/blogs.xml" ]; } ''; description = '' Your <filename>configuration.yaml</filename> as a Nix attribute set. - Beware that setting this option will delete your previous <filename>configuration.yaml</filename>. - <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">Secrets</link> - are encoded as strings as shown in the example. + + YAML functions like <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">secrets</link> + can be passed as a string and will be unquoted automatically. + + Unless this option is explicitly set to <literal>null</literal> + we assume your <filename>configuration.yaml</filename> is + managed through this module and thereby overwritten on startup. ''; }; @@ -147,16 +279,18 @@ in { type = types.bool; description = '' Whether to make <filename>configuration.yaml</filename> writable. - This only has an effect if <option>config</option> is set. + This will allow you to edit it from Home Assistant's web interface. + + This only has an effect if <option>config</option> is set. However, bear in mind that it will be overwritten at every start of the service. ''; }; lovelaceConfig = mkOption { default = null; - type = with types; nullOr attrs; - # from https://www.home-assistant.io/lovelace/yaml-mode/ + type = types.nullOr format.type; + # from https://www.home-assistant.io/lovelace/dashboards/ example = literalExpression '' { title = "My Awesome Home"; @@ -172,8 +306,8 @@ in { ''; description = '' Your <filename>ui-lovelace.yaml</filename> as a Nix attribute set. - Setting this option will automatically add - <literal>lovelace.mode = "yaml";</literal> to your <option>config</option>. + Setting this option will automatically set <literal>lovelace.mode</literal> to <literal>yaml</literal>. + Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename> ''; }; @@ -183,8 +317,10 @@ in { type = types.bool; description = '' Whether to make <filename>ui-lovelace.yaml</filename> writable. - This only has an effect if <option>lovelaceConfig</option> is set. + This will allow you to edit it from Home Assistant's web interface. + + This only has an effect if <option>lovelaceConfig</option> is set. However, bear in mind that it will be overwritten at every start of the service. ''; }; @@ -201,11 +337,18 @@ in { type = types.package; example = literalExpression '' pkgs.home-assistant.override { - extraPackages = ps: with ps; [ colorlog ]; + extraPackages = python3Packages: with python3Packages; [ + psycopg2 + ]; + extraComponents = [ + "default_config" + "esphome" + "met" + ]; } ''; description = '' - Home Assistant package to use. By default the tests are disabled, as they take a considerable amout of time to complete. + The Home Assistant package to use. Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies. If you specify <option>config</option> and do not set <option>autoExtraComponents</option> to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect. @@ -213,21 +356,6 @@ in { ''; }; - autoExtraComponents = mkOption { - default = true; - type = types.bool; - description = '' - If set to <literal>true</literal>, the components used in <literal>config</literal> - are set as the specified package's <literal>extraComponents</literal>. - This in turn adds all packaged dependencies to the derivation. - You might still see import errors in your log. - In this case, you will need to package the necessary dependencies yourself - or ask for someone else to package them. - If a dependency is packaged but not automatically added to this list, - you might need to specify it in <literal>extraPackages</literal>. - ''; - }; - openFirewall = mkOption { default = false; type = types.bool; @@ -240,18 +368,30 @@ in { systemd.services.home-assistant = { description = "Home Assistant"; - after = [ "network.target" ]; - preStart = optionalString (cfg.config != null) (if cfg.configWritable then '' - cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml" - '' else '' - rm -f "${cfg.configDir}/configuration.yaml" - ln -s ${configFile} "${cfg.configDir}/configuration.yaml" - '') + optionalString (cfg.lovelaceConfig != null) (if cfg.lovelaceConfigWritable then '' - cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml" - '' else '' - rm -f "${cfg.configDir}/ui-lovelace.yaml" - ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml" - ''); + after = [ + "network-online.target" + + # prevent races with database creation + "mysql.service" + "postgresql.service" + ]; + preStart = let + copyConfig = if cfg.configWritable then '' + cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml" + '' else '' + rm -f "${cfg.configDir}/configuration.yaml" + ln -s ${configFile} "${cfg.configDir}/configuration.yaml" + ''; + copyLovelaceConfig = if cfg.lovelaceConfigWritable then '' + cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml" + '' else '' + rm -f "${cfg.configDir}/ui-lovelace.yaml" + ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml" + ''; + in + (optionalString (cfg.config != null) copyConfig) + + (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig) + ; serviceConfig = let # List of capabilities to equip home-assistant with, depending on configured components capabilities = [ diff --git a/nixos/modules/services/web-servers/agate.nix b/nixos/modules/services/web-servers/agate.nix new file mode 100644 index 00000000000..3afdb561c0b --- /dev/null +++ b/nixos/modules/services/web-servers/agate.nix @@ -0,0 +1,148 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.agate; +in +{ + options = { + services.agate = { + enable = mkEnableOption "Agate Server"; + + package = mkOption { + type = types.package; + default = pkgs.agate; + defaultText = literalExpression "pkgs.agate"; + description = "The package to use"; + }; + + addresses = mkOption { + type = types.listOf types.str; + default = [ "0.0.0.0:1965" ]; + description = '' + Addresses to listen on, IP:PORT, if you haven't disabled forwarding + only set IPv4. + ''; + }; + + contentDir = mkOption { + default = "/var/lib/agate/content"; + type = types.path; + description = "Root of the content directory."; + }; + + certificatesDir = mkOption { + default = "/var/lib/agate/certificates"; + type = types.path; + description = "Root of the certificate directory."; + }; + + hostnames = mkOption { + default = [ ]; + type = types.listOf types.str; + description = '' + Domain name of this Gemini server, enables checking hostname and port + in requests. (multiple occurences means basic vhosts) + ''; + }; + + language = mkOption { + default = null; + type = types.nullOr types.str; + description = "RFC 4646 Language code for text/gemini documents."; + }; + + onlyTls_1_3 = mkOption { + default = false; + type = types.bool; + description = "Only use TLSv1.3 (default also allows TLSv1.2)."; + }; + + extraArgs = mkOption { + type = types.listOf types.str; + default = [ "" ]; + example = [ "--log-ip" ]; + description = "Extra arguments to use running agate."; + }; + }; + }; + + config = mkIf cfg.enable { + # available for generating certs by hand + # it can be a bit arduous with openssl + environment.systemPackages = [ cfg.package ]; + + systemd.services.agate = { + description = "Agate"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "network-online.target" ]; + + script = + let + prefixKeyList = key: list: concatMap (v: [ key v ]) list; + addresses = prefixKeyList "--addr" cfg.addresses; + hostnames = prefixKeyList "--hostname" cfg.hostnames; + in + '' + exec ${cfg.package}/bin/agate ${ + escapeShellArgs ( + [ + "--content" "${cfg.contentDir}" + "--certs" "${cfg.certificatesDir}" + ] ++ + addresses ++ + (optionals (cfg.hostnames != []) hostnames) ++ + (optionals (cfg.language != null) [ "--lang" cfg.language ]) ++ + (optionals cfg.onlyTls_1_3 [ "--only-tls13" ]) ++ + (optionals (cfg.extraArgs != []) cfg.extraArgs) + ) + } + ''; + + serviceConfig = { + Restart = "always"; + RestartSec = "5s"; + DynamicUser = true; + StateDirectory = "agate"; + + # Security options: + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + + # ProtectClock= adds DeviceAllow=char-rtc r + DeviceAllow = ""; + + LockPersonality = true; + + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + + RestrictNamespaces = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictRealtime = true; + + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = [ + "@system-service" + "~@cpu-emulation" + "~@debug" + "~@keyring" + "~@memlock" + "~@obsolete" + "~@privileged" + "~@setuid" + ]; + }; + }; + }; +} diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 2ea871626e2..68333d43a5d 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -2,7 +2,6 @@ use strict; use warnings; -use Array::Compare; use Config::IniFiles; use File::Path qw(make_path); use File::Basename; @@ -221,9 +220,13 @@ sub unrecord_unit { # - 2 if the units are different and a reload action is required sub compare_units { my ($old_unit, $new_unit) = @_; - my $comp = Array::Compare->new; my $ret = 0; + my $comp_array = sub { + my ($a, $b) = @_; + return join("\0", @{$a}) eq join("\0", @{$b}); + }; + # Comparison hash for the sections my %section_cmp = map { $_ => 1 } keys %{$new_unit}; # Iterate over the sections @@ -255,7 +258,7 @@ sub compare_units { } my @new_value = @{$new_unit->{$section_name}{$ini_key}}; # If the contents are different, the units are different - if (not $comp->compare(\@old_value, \@new_value)) { + if (not $comp_array->(\@old_value, \@new_value)) { # Check if only the reload triggers changed if ($section_name eq 'Unit' and $ini_key eq 'X-Reload-Triggers') { $ret = 2; diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix index 4745239050b..b8aeee8c11b 100644 --- a/nixos/modules/system/activation/top-level.nix +++ b/nixos/modules/system/activation/top-level.nix @@ -117,7 +117,7 @@ let configurationName = config.boot.loader.grub.configurationName; # Needed by switch-to-configuration. - perl = pkgs.perl.withPackages (p: with p; [ ArrayCompare ConfigIniFiles FileSlurp NetDBus ]); + perl = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp NetDBus ]); }; # Handle assertions and warnings diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 3fd4945ed35..520c48bc45b 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -32,6 +32,7 @@ in acme = handleTest ./acme.nix {}; adguardhome = handleTest ./adguardhome.nix {}; aesmd = handleTest ./aesmd.nix {}; + agate = handleTest ./web-servers/agate.nix {}; agda = handleTest ./agda.nix {}; airsonic = handleTest ./airsonic.nix {}; amazon-init-shell = handleTest ./amazon-init-shell.nix {}; diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix index 5b1c07c92da..31f8a4bcc19 100644 --- a/nixos/tests/home-assistant.nix +++ b/nixos/tests/home-assistant.nix @@ -10,6 +10,7 @@ in { nodes.hass = { pkgs, ... }: { environment.systemPackages = with pkgs; [ mosquitto ]; + services.mosquitto = { enable = true; listeners = [ { @@ -21,14 +22,42 @@ in { }; } ]; }; + + services.postgresql = { + enable = true; + ensureDatabases = [ "hass" ]; + ensureUsers = [{ + name = "hass"; + ensurePermissions = { + "DATABASE hass" = "ALL PRIVILEGES"; + }; + }]; + }; + services.home-assistant = { - inherit configDir; enable = true; + inherit configDir; + + # tests loading components by overriding the package package = (pkgs.home-assistant.override { + extraPackages = ps: with ps; [ + colorama + ]; extraComponents = [ "zha" ]; }).overrideAttrs (oldAttrs: { doInstallCheck = false; }); + + # tests loading components from the module + extraComponents = [ + "wake_on_lan" + ]; + + # test extra package passing from the module + extraPackages = python3Packages: with python3Packages; [ + psycopg2 + ]; + config = { homeassistant = { name = "Home"; @@ -37,34 +66,58 @@ in { longitude = "0.0"; elevation = 0; }; + + # configure the recorder component to use the postgresql db + recorder.db_url = "postgresql://@/hass"; + + # we can't load default_config, because the updater requires + # network access and would cause an error, so load frontend + # here explicitly. + # https://www.home-assistant.io/integrations/frontend/ frontend = {}; + + # configure an mqtt broker connection + # https://www.home-assistant.io/integrations/mqtt mqtt = { broker = "127.0.0.1"; username = mqttUsername; password = mqttPassword; }; - binary_sensor = [{ + + # create a mqtt sensor that syncs state with its mqtt topic + # https://www.home-assistant.io/integrations/sensor.mqtt/ + binary_sensor = [ { platform = "mqtt"; state_topic = "home-assistant/test"; payload_on = "let_there_be_light"; payload_off = "off"; - }]; - wake_on_lan = {}; - switch = [{ + } ]; + + # set up a wake-on-lan switch to test capset capability required + # for the ping suid wrapper + # https://www.home-assistant.io/integrations/wake_on_lan/ + switch = [ { platform = "wake_on_lan"; mac = "00:11:22:33:44:55"; host = "127.0.0.1"; - }]; - # tests component-based capability assignment (CAP_NET_BIND_SERVICE) + } ]; + + # test component-based capability assignment (CAP_NET_BIND_SERVICE) + # https://www.home-assistant.io/integrations/emulated_hue/ emulated_hue = { host_ip = "127.0.0.1"; listen_port = 80; }; + + # show mqtt interaction in the log + # https://www.home-assistant.io/integrations/logger/ logger = { default = "info"; logs."homeassistant.components.mqtt" = "debug"; }; }; + + # configure the sample lovelace dashboard lovelaceConfig = { title = "My Awesome Home"; views = [{ @@ -81,34 +134,57 @@ in { }; testScript = '' + import re + start_all() + + # Parse the package path out of the systemd unit, as we cannot + # access the final package, that is overriden inside the module, + # by any other means. + pattern = re.compile(r"path=(?P<path>[\/a-z0-9-.]+)\/bin\/hass") + response = hass.execute("systemctl show -p ExecStart home-assistant.service")[1] + match = pattern.search(response) + package = match.group('path') + hass.wait_for_unit("home-assistant.service") + with subtest("Check that YAML configuration file is in place"): hass.succeed("test -L ${configDir}/configuration.yaml") - with subtest("lovelace config is copied because lovelaceConfigWritable = true"): + + with subtest("Check the lovelace config is copied because lovelaceConfigWritable = true"): hass.succeed("test -f ${configDir}/ui-lovelace.yaml") + + with subtest("Check extraComponents and extraPackages are considered from the package"): + hass.succeed(f"grep -q 'colorama' {package}/extra_packages") + hass.succeed(f"grep -q 'zha' {package}/extra_components") + + with subtest("Check extraComponents and extraPackages are considered from the module"): + hass.succeed(f"grep -q 'psycopg2' {package}/extra_packages") + hass.succeed(f"grep -q 'wake_on_lan' {package}/extra_components") + with subtest("Check that Home Assistant's web interface and API can be reached"): + hass.wait_until_succeeds("journalctl -u home-assistant.service | grep -q 'Home Assistant initialized in'") hass.wait_for_open_port(8123) hass.succeed("curl --fail http://localhost:8123/lovelace") + with subtest("Toggle a binary sensor using MQTT"): hass.wait_for_open_port(1883) hass.succeed( "mosquitto_pub -V mqttv5 -t home-assistant/test -u ${mqttUsername} -P '${mqttPassword}' -m let_there_be_light" ) + with subtest("Check that capabilities are passed for emulated_hue to bind to port 80"): hass.wait_for_open_port(80) hass.succeed("curl --fail http://localhost:80/description.xml") + with subtest("Check extra components are considered in systemd unit hardening"): hass.succeed("systemctl show -p DeviceAllow home-assistant.service | grep -q char-ttyUSB") + with subtest("Print log to ease debugging"): output_log = hass.succeed("cat ${configDir}/home-assistant.log") print("\n### home-assistant.log ###\n") print(output_log + "\n") - # wait for home-assistant to fully boot - hass.sleep(30) - hass.wait_for_unit("home-assistant.service") - with subtest("Check that no errors were logged"): assert "ERROR" not in output_log @@ -117,7 +193,7 @@ in { assert "let_there_be_light" in output_log with subtest("Check systemd unit hardening"): - hass.log(hass.succeed("systemctl show home-assistant.service")) + hass.log(hass.succeed("systemctl cat home-assistant.service")) hass.log(hass.succeed("systemd-analyze security home-assistant.service")) ''; }) diff --git a/nixos/tests/web-servers/agate.nix b/nixos/tests/web-servers/agate.nix new file mode 100644 index 00000000000..e364e134cfd --- /dev/null +++ b/nixos/tests/web-servers/agate.nix @@ -0,0 +1,29 @@ +import ../make-test-python.nix ( + { pkgs, lib, ... }: + { + name = "agate"; + meta = with lib.maintainers; { maintainers = [ jk ]; }; + + nodes = { + geminiserver = { pkgs, ... }: { + services.agate = { + enable = true; + hostnames = [ "localhost" ]; + contentDir = pkgs.writeTextDir "index.gmi" '' + # Hello NixOS! + ''; + }; + }; + }; + + testScript = { nodes, ... }: '' + geminiserver.wait_for_unit("agate") + geminiserver.wait_for_open_port(1965) + + with subtest("check is serving over gemini"): + response = geminiserver.succeed("${pkgs.gmni}/bin/gmni -j once -i -N gemini://localhost:1965") + print(response) + assert "Hello NixOS!" in response + ''; + } +) |