diff --git a/format b/format index ff4b32e47..4f46c7b9e 100755 --- a/format +++ b/format @@ -26,15 +26,9 @@ done # The excludes are for files touched by open pull requests and we want # to avoid merge conflicts. excludes=( - modules/default.nix modules/files.nix modules/home-environment.nix - modules/lib/default.nix - modules/lib/file-type.nix - modules/misc/news.nix - modules/programs/ssh.nix modules/programs/zsh.nix - tests/default.nix ) exclude_args=() diff --git a/modules/default.nix b/modules/default.nix index 02be152fd..ba43fec5c 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,12 +1,9 @@ -{ configuration -, pkgs -, lib ? pkgs.lib +{ configuration, pkgs, lib ? pkgs.lib # Whether to check that each option has a matching declaration. , check ? true # Extra arguments passed to specialArgs. -, extraSpecialArgs ? { } -}: +, extraSpecialArgs ? { } }: with lib; @@ -16,39 +13,33 @@ let map (x: x.message) (filter (x: !x.assertion) cfg.assertions); showWarnings = res: - let - f = w: x: builtins.trace "warning: ${w}" x; - in - fold f res res.config.warnings; + let f = w: x: builtins.trace "warning: ${w}" x; + in fold f res res.config.warnings; extendedLib = import ./lib/stdlib-extended.nix lib; - hmModules = - import ./modules.nix { - inherit check pkgs; - lib = extendedLib; - }; + hmModules = import ./modules.nix { + inherit check pkgs; + lib = extendedLib; + }; rawModule = extendedLib.evalModules { modules = [ configuration ] ++ hmModules; - specialArgs = { - modulesPath = builtins.toString ./.; - } // extraSpecialArgs; + specialArgs = { modulesPath = builtins.toString ./.; } // extraSpecialArgs; }; - module = showWarnings ( - let - failed = collectFailed rawModule.config; - failedStr = concatStringsSep "\n" (map (x: "- ${x}") failed); - in - if failed == [] - then rawModule - else throw "\nFailed assertions:\n${failedStr}" - ); + module = showWarnings (let + failed = collectFailed rawModule.config; + failedStr = concatStringsSep "\n" (map (x: "- ${x}") failed); + in if failed == [ ] then + rawModule + else + throw '' -in + Failed assertions: + ${failedStr}''); -{ +in { inherit (module) options config; activationPackage = module.config.home.activationPackage; @@ -57,10 +48,8 @@ in activation-script = module.config.home.activationPackage; newsDisplay = rawModule.config.news.display; - newsEntries = - sort (a: b: a.time > b.time) ( - filter (a: a.condition) rawModule.config.news.entries - ); + newsEntries = sort (a: b: a.time > b.time) + (filter (a: a.condition) rawModule.config.news.entries); inherit (module._module.args) pkgs; } diff --git a/modules/lib/file-type.nix b/modules/lib/file-type.nix index cd3a3a627..3fb51fd56 100644 --- a/modules/lib/file-type.nix +++ b/modules/lib/file-type.nix @@ -1,9 +1,9 @@ { homeDirectory, lib, pkgs }: let - inherit (lib) hasPrefix hm literalExpression mkDefault mkIf mkOption removePrefix types; -in -{ + inherit (lib) + hasPrefix hm literalExpression mkDefault mkIf mkOption removePrefix types; +in { # Constructs a type suitable for a `home.file` like option. The # target path may be either absolute or relative, in which case it # is relative the `basePath` argument (which itself must be an @@ -13,8 +13,8 @@ in # - opt the name of the option, for self-references # - basePathDesc docbook compatible description of the base path # - basePath the file base path - fileType = opt: basePathDesc: basePath: types.attrsOf (types.submodule ( - { name, config, ... }: { + fileType = opt: basePathDesc: basePath: + types.attrsOf (types.submodule ({ name, config, ... }: { options = { enable = mkOption { type = types.bool; @@ -27,10 +27,8 @@ in target = mkOption { type = types.str; apply = p: - let - absPath = if hasPrefix "/" p then p else "${basePath}/${p}"; - in - removePrefix (homeDirectory + "/") absPath; + let absPath = if hasPrefix "/" p then p else "${basePath}/${p}"; + in removePrefix (homeDirectory + "/") absPath; defaultText = literalExpression "name"; description = '' Path to target file relative to ${basePathDesc}. @@ -113,14 +111,11 @@ in config = { target = mkDefault name; - source = mkIf (config.text != null) ( - mkDefault (pkgs.writeTextFile { - inherit (config) text; - executable = config.executable == true; # can be null - name = hm.strings.storeFileName name; - }) - ); + source = mkIf (config.text != null) (mkDefault (pkgs.writeTextFile { + inherit (config) text; + executable = config.executable == true; # can be null + name = hm.strings.storeFileName name; + })); }; - } - )); + })); } diff --git a/modules/misc/news.nix b/modules/misc/news.nix index 4e0f08a75..ed6d1546b 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -42,14 +42,10 @@ let }; }; - config = { - id = mkDefault (builtins.hashString "sha256" config.message); - }; + config = { id = mkDefault (builtins.hashString "sha256" config.message); }; }); -in - -{ +in { meta.maintainers = [ maintainers.rycee ]; options = { @@ -97,9 +93,8 @@ in }; config = { - news.json.output = pkgs.writeText "hm-news.json" (builtins.toJSON { - inherit (cfg) display entries; - }); + news.json.output = pkgs.writeText "hm-news.json" + (builtins.toJSON { inherit (cfg) display entries; }); # Add news entries in chronological order (i.e., latest time # should be at the bottom of the list). The time should be @@ -255,7 +250,8 @@ in { time = "2021-09-23T17:04:48+00:00"; - condition = hostPlatform.isLinux && config.services.screen-locker.enable; + condition = hostPlatform.isLinux + && config.services.screen-locker.enable; message = '' 'xautolock' is now optional in 'services.screen-locker', and the 'services.screen-locker' options have been reorganized for clarity. diff --git a/modules/programs/ssh.nix b/modules/programs/ssh.nix index 5d038075f..e062b2324 100644 --- a/modules/programs/ssh.nix +++ b/modules/programs/ssh.nix @@ -9,17 +9,17 @@ let isPath = x: builtins.substring 0 1 (toString x) == "/"; addressPort = entry: - if isPath entry.address - then " ${entry.address}" - else " [${entry.address}]:${toString entry.port}"; + if isPath entry.address then + " ${entry.address}" + else + " [${entry.address}]:${toString entry.port}"; unwords = builtins.concatStringsSep " "; - mkSetEnvStr = envStr: unwords - (mapAttrsToList - (name: value: ''${name}="${escape [ "\"" "\\" ] (toString value)}"'') - envStr - ); + mkSetEnvStr = envStr: + unwords (mapAttrsToList + (name: value: ''${name}="${escape [ ''"'' "\\" ] (toString value)}"'') + envStr); bindOptions = { address = mkOption { @@ -37,9 +37,7 @@ let }; }; - dynamicForwardModule = types.submodule { - options = bindOptions; - }; + dynamicForwardModule = types.submodule { options = bindOptions; }; forwardModule = types.submodule { options = { @@ -83,7 +81,9 @@ let match = mkOption { type = types.nullOr types.str; default = null; - example = "host canonical\nhost exec \"ping -c1 -q 192.168.17.1\""; + example = '' + host canonical + host exec "ping -c1 -q 192.168.17.1"''; description = '' `Match` block conditions used by this block. See {manpage}`ssh_config(5)` @@ -141,11 +141,8 @@ let identityFile = mkOption { type = with types; either (listOf str) (nullOr str); - default = []; - apply = p: - if p == null then [] - else if isString p then [p] - else p; + default = [ ]; + apply = p: if p == null then [ ] else if isString p then [ p ] else p; description = '' Specifies files from which the user identity is read. Identities will be tried in the given order. @@ -182,7 +179,7 @@ let sendEnv = mkOption { type = types.listOf types.str; - default = []; + default = [ ]; description = '' Environment variables to send from the local host to the server. @@ -191,7 +188,7 @@ let setEnv = mkOption { type = with types; attrsOf (oneOf [ str path int float ]); - default = {}; + default = { }; description = '' Environment variables and their value to send to the server. ''; @@ -229,11 +226,8 @@ let certificateFile = mkOption { type = with types; either (listOf str) (nullOr str); - default = []; - apply = p: - if p == null then [] - else if isString p then [p] - else p; + default = [ ]; + apply = p: if p == null then [ ] else if isString p then [ p ] else p; description = '' Specifies files from which the user certificate is read. ''; @@ -241,7 +235,7 @@ let addressFamily = mkOption { default = null; - type = types.nullOr (types.enum ["any" "inet" "inet6"]); + type = types.nullOr (types.enum [ "any" "inet" "inet6" ]); description = '' Specifies which address family to use when connecting. ''; @@ -249,7 +243,7 @@ let localForwards = mkOption { type = types.listOf forwardModule; - default = []; + default = [ ]; example = literalExpression '' [ { @@ -267,7 +261,7 @@ let remoteForwards = mkOption { type = types.listOf forwardModule; - default = []; + default = [ ]; example = literalExpression '' [ { @@ -285,7 +279,7 @@ let dynamicForwards = mkOption { type = types.listOf dynamicForwardModule; - default = []; + default = [ ]; example = literalExpression '' [ { port = 8080; } ]; ''; @@ -297,50 +291,52 @@ let extraOptions = mkOption { type = types.attrsOf types.str; - default = {}; + default = { }; description = "Extra configuration options for the host."; }; }; -# config.host = mkDefault dagName; + # config.host = mkDefault dagName; }); - matchBlockStr = key: cf: concatStringsSep "\n" ( - let + matchBlockStr = key: cf: + concatStringsSep "\n" (let hostOrDagName = if cf.host != null then cf.host else key; - matchHead = if cf.match != null - then "Match ${cf.match}" - else "Host ${hostOrDagName}"; + matchHead = if cf.match != null then + "Match ${cf.match}" + else + "Host ${hostOrDagName}"; in [ "${matchHead}" ] - ++ optional (cf.port != null) " Port ${toString cf.port}" - ++ optional (cf.forwardAgent != null) " ForwardAgent ${lib.hm.booleans.yesNo cf.forwardAgent}" - ++ optional cf.forwardX11 " ForwardX11 yes" - ++ optional cf.forwardX11Trusted " ForwardX11Trusted yes" - ++ optional cf.identitiesOnly " IdentitiesOnly yes" - ++ optional (cf.user != null) " User ${cf.user}" - ++ optional (cf.hostname != null) " HostName ${cf.hostname}" - ++ optional (cf.addressFamily != null) " AddressFamily ${cf.addressFamily}" - ++ optional (cf.sendEnv != []) " SendEnv ${unwords cf.sendEnv}" - ++ optional (cf.setEnv != {}) " SetEnv ${mkSetEnvStr cf.setEnv}" + ++ optional (cf.port != null) " Port ${toString cf.port}" + ++ optional (cf.forwardAgent != null) + " ForwardAgent ${lib.hm.booleans.yesNo cf.forwardAgent}" + ++ optional cf.forwardX11 " ForwardX11 yes" + ++ optional cf.forwardX11Trusted " ForwardX11Trusted yes" + ++ optional cf.identitiesOnly " IdentitiesOnly yes" + ++ optional (cf.user != null) " User ${cf.user}" + ++ optional (cf.hostname != null) " HostName ${cf.hostname}" + ++ optional (cf.addressFamily != null) " AddressFamily ${cf.addressFamily}" + ++ optional (cf.sendEnv != [ ]) " SendEnv ${unwords cf.sendEnv}" + ++ optional (cf.setEnv != { }) " SetEnv ${mkSetEnvStr cf.setEnv}" ++ optional (cf.serverAliveInterval != 0) - " ServerAliveInterval ${toString cf.serverAliveInterval}" + " ServerAliveInterval ${toString cf.serverAliveInterval}" ++ optional (cf.serverAliveCountMax != 3) - " ServerAliveCountMax ${toString cf.serverAliveCountMax}" - ++ optional (cf.compression != null) " Compression ${lib.hm.booleans.yesNo cf.compression}" - ++ optional (!cf.checkHostIP) " CheckHostIP no" - ++ optional (cf.proxyCommand != null) " ProxyCommand ${cf.proxyCommand}" - ++ optional (cf.proxyJump != null) " ProxyJump ${cf.proxyJump}" + " ServerAliveCountMax ${toString cf.serverAliveCountMax}" + ++ optional (cf.compression != null) + " Compression ${lib.hm.booleans.yesNo cf.compression}" + ++ optional (!cf.checkHostIP) " CheckHostIP no" + ++ optional (cf.proxyCommand != null) " ProxyCommand ${cf.proxyCommand}" + ++ optional (cf.proxyJump != null) " ProxyJump ${cf.proxyJump}" ++ map (file: " IdentityFile ${file}") cf.identityFile ++ map (file: " CertificateFile ${file}") cf.certificateFile - ++ map (f: " LocalForward" + addressPort f.bind + addressPort f.host) cf.localForwards - ++ map (f: " RemoteForward" + addressPort f.bind + addressPort f.host) cf.remoteForwards + ++ map (f: " LocalForward" + addressPort f.bind + addressPort f.host) + cf.localForwards + ++ map (f: " RemoteForward" + addressPort f.bind + addressPort f.host) + cf.remoteForwards ++ map (f: " DynamicForward" + addressPort f) cf.dynamicForwards - ++ mapAttrsToList (n: v: " ${n} ${v}") cf.extraOptions - ); + ++ mapAttrsToList (n: v: " ${n} ${v}") cf.extraOptions); -in - -{ +in { meta.maintainers = [ maintainers.rycee ]; options.programs.ssh = { @@ -349,7 +345,8 @@ in package = mkPackageOption pkgs "openssh" { nullable = true; default = null; - extraDescription = "By default, the client provided by your system is used."; + extraDescription = + "By default, the client provided by your system is used."; }; forwardAgent = mkOption { @@ -418,7 +415,7 @@ in controlMaster = mkOption { default = "no"; - type = types.enum ["yes" "no" "ask" "auto" "autoask"]; + type = types.enum [ "yes" "no" "ask" "auto" "autoask" ]; description = '' Configure sharing of multiple sessions over a single network connection. ''; @@ -451,7 +448,7 @@ in extraOptionOverrides = mkOption { type = types.attrsOf types.str; - default = {}; + default = { }; description = '' Extra SSH configuration options that take precedence over any host specific configuration. @@ -460,7 +457,7 @@ in includes = mkOption { type = types.listOf types.str; - default = []; + default = [ ]; description = '' File globs of ssh config files that should be included via the `Include` directive. @@ -473,7 +470,7 @@ in matchBlocks = mkOption { type = hm.types.dagOf matchBlockModule; - default = {}; + default = { }; example = literalExpression '' { "john.example.com" = { @@ -499,43 +496,41 @@ in }; config = mkIf cfg.enable { - assertions = [ - { - assertion = - let - # `builtins.any`/`lib.lists.any` does not return `true` if there are no elements. - any' = pred: items: if items == [] then true else any pred items; - # Check that if `entry.address` is defined, and is a path, that `entry.port` has not - # been defined. - noPathWithPort = entry: entry.address != null && isPath entry.address -> entry.port == null; - checkDynamic = block: any' noPathWithPort block.dynamicForwards; - checkBindAndHost = fwd: noPathWithPort fwd.bind && noPathWithPort fwd.host; - checkLocal = block: any' checkBindAndHost block.localForwards; - checkRemote = block: any' checkBindAndHost block.remoteForwards; - checkMatchBlock = block: all (fn: fn block) [ checkLocal checkRemote checkDynamic ]; - in any' checkMatchBlock (map (block: block.data) (builtins.attrValues cfg.matchBlocks)); - message = "Forwarded paths cannot have ports."; - } - ]; + assertions = [{ + assertion = let + # `builtins.any`/`lib.lists.any` does not return `true` if there are no elements. + any' = pred: items: if items == [ ] then true else any pred items; + # Check that if `entry.address` is defined, and is a path, that `entry.port` has not + # been defined. + noPathWithPort = entry: + entry.address != null && isPath entry.address -> entry.port == null; + checkDynamic = block: any' noPathWithPort block.dynamicForwards; + checkBindAndHost = fwd: + noPathWithPort fwd.bind && noPathWithPort fwd.host; + checkLocal = block: any' checkBindAndHost block.localForwards; + checkRemote = block: any' checkBindAndHost block.remoteForwards; + checkMatchBlock = block: + all (fn: fn block) [ checkLocal checkRemote checkDynamic ]; + in any' checkMatchBlock + (map (block: block.data) (builtins.attrValues cfg.matchBlocks)); + message = "Forwarded paths cannot have ports."; + }]; home.packages = optional (cfg.package != null) cfg.package; - home.file.".ssh/config".text = - let - sortedMatchBlocks = hm.dag.topoSort cfg.matchBlocks; - sortedMatchBlocksStr = builtins.toJSON sortedMatchBlocks; - matchBlocks = - if sortedMatchBlocks ? result - then sortedMatchBlocks.result - else abort "Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}"; - in '' - ${concatStringsSep "\n" ( - (mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides) + home.file.".ssh/config".text = let + sortedMatchBlocks = hm.dag.topoSort cfg.matchBlocks; + sortedMatchBlocksStr = builtins.toJSON sortedMatchBlocks; + matchBlocks = if sortedMatchBlocks ? result then + sortedMatchBlocks.result + else + abort "Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}"; + in '' + ${concatStringsSep "\n" + ((mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides) ++ (optional (cfg.includes != [ ]) '' Include ${concatStringsSep " " cfg.includes} - '') - ++ (map (block: matchBlockStr block.name block.data) matchBlocks) - )} + '') ++ (map (block: matchBlockStr block.name block.data) matchBlocks))} Host * ForwardAgent ${lib.hm.booleans.yesNo cfg.forwardAgent} @@ -549,11 +544,13 @@ in ControlPath ${cfg.controlPath} ControlPersist ${cfg.controlPersist} - ${replaceStrings ["\n"] ["\n "] cfg.extraConfig} + ${replaceStrings [ "\n" ] [ "\n " ] cfg.extraConfig} ''; - warnings = mapAttrsToList - (n: v: "The SSH config match block `programs.ssh.matchBlocks.${n}` sets both of the host and match options.\nThe match option takes precedence.") - (filterAttrs (n: v: v.data.host != null && v.data.match != null) cfg.matchBlocks); + warnings = mapAttrsToList (n: v: '' + The SSH config match block `programs.ssh.matchBlocks.${n}` sets both of the host and match options. + The match option takes precedence.'') + (filterAttrs (n: v: v.data.host != null && v.data.match != null) + cfg.matchBlocks); }; } diff --git a/tests/default.nix b/tests/default.nix index d5a5be706..4809364a8 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -1,4 +1,4 @@ -{ pkgs ? import {}, enableBig ? true }: +{ pkgs ? import { }, enableBig ? true }: let @@ -13,37 +13,33 @@ let modules = import ../modules/modules.nix { inherit lib pkgs; check = false; - } ++ [ - { - # Bypass reference inside modules/modules.nix to make the test - # suite more pure. - _module.args.pkgsPath = pkgs.path; + } ++ [{ + # Bypass reference inside modules/modules.nix to make the test + # suite more pure. + _module.args.pkgsPath = pkgs.path; - # Fix impurities. Without these some of the user's environment - # will leak into the tests through `builtins.getEnv`. - xdg.enable = true; - home = { - username = "hm-user"; - homeDirectory = "/home/hm-user"; - stateVersion = lib.mkDefault "18.09"; - }; + # Fix impurities. Without these some of the user's environment + # will leak into the tests through `builtins.getEnv`. + xdg.enable = true; + home = { + username = "hm-user"; + homeDirectory = "/home/hm-user"; + stateVersion = lib.mkDefault "18.09"; + }; - # Avoid including documentation since this will cause - # unnecessary rebuilds of the tests. - manual.manpages.enable = lib.mkDefault false; + # Avoid including documentation since this will cause + # unnecessary rebuilds of the tests. + manual.manpages.enable = lib.mkDefault false; - imports = [ ./asserts.nix ./big-test.nix ./stubs.nix ]; + imports = [ ./asserts.nix ./big-test.nix ./stubs.nix ]; - test.enableBig = enableBig; - } - ]; + test.enableBig = enableBig; + }]; isDarwin = pkgs.stdenv.hostPlatform.isDarwin; isLinux = pkgs.stdenv.hostPlatform.isLinux; -in - -import nmt { +in import nmt { inherit lib pkgs modules; testedAttrPath = [ "home" "activationPackage" ]; tests = builtins.foldl' (a: b: a // (import b)) { } ([ @@ -180,7 +176,7 @@ import nmt { ./modules/programs/abook ./modules/programs/autorandr ./modules/programs/awscli - ./modules/programs/beets # One test relies on services.mpd + ./modules/programs/beets # One test relies on services.mpd ./modules/programs/bemenu ./modules/programs/borgmatic ./modules/programs/boxxy