···751 t' = opt.options.type;
752 mergedType = t.typeMerge t'.functor;
753 typesMergeable = mergedType != null;
754- typeSet = if (bothHave "type") && typesMergeable
755- then { type = mergedType; }
756- else {};
000000000000000000000000000000757 bothHave = k: opt.options ? ${k} && res ? ${k};
758 in
759 if bothHave "default" ||
760 bothHave "example" ||
761 bothHave "description" ||
762- bothHave "apply" ||
763- (bothHave "type" && (! typesMergeable))
764 then
0765 throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
766 else
767 let
···751 t' = opt.options.type;
752 mergedType = t.typeMerge t'.functor;
753 typesMergeable = mergedType != null;
754+755+ # TODO: Remove this when all downstream reliances of internals: 'functor.wrapped' are sufficiently migrated.
756+ # A function that adds the deprecated wrapped message to a type.
757+ addDeprecatedWrapped = t:
758+ t // {
759+ functor = t.functor // {
760+ wrapped = t.functor.wrappedDeprecationMessage {
761+ inherit loc;
762+ };
763+ };
764+ };
765+766+ typeSet =
767+ if opt.options ? type then
768+ if res ? type then
769+ if typesMergeable then
770+ {
771+ type =
772+ if mergedType ? functor.wrappedDeprecationMessage then
773+ addDeprecatedWrapped mergedType
774+ else
775+ mergedType;
776+ }
777+ else
778+ # Keep in sync with the same error below!
779+ throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
780+ else if opt.options.type ? functor.wrappedDeprecationMessage then
781+ { type = addDeprecatedWrapped opt.options.type; }
782+ else
783+ {}
784+ else
785+ {};
786+787 bothHave = k: opt.options ? ${k} && res ? ${k};
788 in
789 if bothHave "default" ||
790 bothHave "example" ||
791 bothHave "description" ||
792+ bothHave "apply"
0793 then
794+ # Keep in sync with the same error above!
795 throw "The option `${showOption loc}' in `${opt._file}' is already declared in ${showFiles res.declarations}."
796 else
797 let
+9
lib/tests/modules.sh
···386checkConfigOutput '^false$' config.conditionalWorks ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
387checkConfigOutput '^"empty"$' config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
38800000000389390# Even with multiple assignments, a type error should be thrown if any of them aren't valid
391checkConfigError 'A definition for option .* is not of type .*' \
···574checkConfigOutput '^38|27$' options.submoduleLine38.declarationPositions.1.line ./declaration-positions.nix
575# nested options work
576checkConfigOutput '^34$' options.nested.nestedLine34.declarationPositions.0.line ./declaration-positions.nix
0577578cat <<EOF
579====== module tests ======
···386checkConfigOutput '^false$' config.conditionalWorks ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
387checkConfigOutput '^"empty"$' config.value.foo ./declare-lazyAttrsOf.nix ./attrsOf-conditional-check.nix
388389+# Check attrsWith type merging
390+checkConfigError 'The option `mergedLazyNonLazy'\'' in `.*'\'' is already declared in `.*'\''\.' options.mergedLazyNonLazy ./lazy-attrsWith.nix
391+checkConfigOutput '^11$' config.lazyResult ./lazy-attrsWith.nix
392+checkConfigError 'infinite recursion encountered' config.nonLazyResult ./lazy-attrsWith.nix
393+394+# Test the attrsOf functor.wrapped warning
395+# shellcheck disable=2016
396+NIX_ABORT_ON_WARN=1 checkConfigError 'The deprecated `type.functor.wrapped` attribute of the option `mergedLazyLazy` is accessed, use `nestedTypes.elemType` instead.' options.mergedLazyLazy.type.functor.wrapped ./lazy-attrsWith.nix
397398# Even with multiple assignments, a type error should be thrown if any of them aren't valid
399checkConfigError 'A definition for option .* is not of type .*' \
···582checkConfigOutput '^38|27$' options.submoduleLine38.declarationPositions.1.line ./declaration-positions.nix
583# nested options work
584checkConfigOutput '^34$' options.nested.nestedLine34.declarationPositions.0.line ./declaration-positions.nix
585+586587cat <<EOF
588====== module tests ======
···83 # Default type merging function
84 # takes two type functors and return the merged type
85 defaultTypeMerge = f: f':
86- let mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor;
87- mergedPayload = f.binOp f.payload f'.payload;
00008889- hasPayload = assert (f'.payload != null) == (f.payload != null); f.payload != null;
90- hasWrapped = assert (f'.wrapped != null) == (f.wrapped != null); f.wrapped != null;
91 in
92 # Abort early: cannot merge different types
93 if f.name != f'.name
···95 else
9697 if hasPayload then
98- if hasWrapped then
00099 # Has both wrapped and payload
100 throw ''
101 Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported.
102103 Use either `functor.payload` or `functor.wrapped` but not both.
104105- If your code worked before remove `functor.payload` from the type definition.
106 ''
107 else
108- # Has payload
109- if mergedPayload == null then null else f.type mergedPayload
110 else
111 if hasWrapped then
112- # Has wrapped
113- # TODO(@hsjobeki): This could also be a warning and removed in the future
114- if mergedWrapped == null then null else f.type mergedWrapped
115 else
116 f.type;
117···582 substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
583 };
584585- attrsOf = elemType: mkOptionType rec {
586- name = "attrsOf";
587- description = "attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
588- descriptionClass = "composite";
589- check = isAttrs;
590- merge = loc: defs:
591- mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
592- (mergeDefinitions (loc ++ [name]) elemType defs).optionalValue
593- )
594- # Push down position info.
595- (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs)));
596- emptyValue = { value = {}; };
597- getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
598- getSubModules = elemType.getSubModules;
599- substSubModules = m: attrsOf (elemType.substSubModules m);
600- functor = (defaultFunctor name) // { wrapped = elemType; };
601- nestedTypes.elemType = elemType;
602- };
603604 # A version of attrsOf that's lazy in its values at the expense of
605 # conditional definitions not working properly. E.g. defining a value with
606 # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
607 # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
608 # error that it's not defined. Use only if conditional definitions don't make sense.
609- lazyAttrsOf = elemType: mkOptionType rec {
610- name = "lazyAttrsOf";
611- description = "lazy attribute set of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
000000000000000000000000000612 descriptionClass = "composite";
613 check = isAttrs;
614- merge = loc: defs:
615- zipAttrsWith (name: defs:
616- let merged = mergeDefinitions (loc ++ [name]) elemType defs;
617- # mergedValue will trigger an appropriate error when accessed
618- in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
619- )
620- # Push down position info.
621- (map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value) defs);
00000000000622 emptyValue = { value = {}; };
623 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
624 getSubModules = elemType.getSubModules;
625- substSubModules = m: lazyAttrsOf (elemType.substSubModules m);
626- functor = (defaultFunctor name) // { wrapped = elemType; };
000000000627 nestedTypes.elemType = elemType;
628 };
629
···83 # Default type merging function
84 # takes two type functors and return the merged type
85 defaultTypeMerge = f: f':
86+ let
87+ mergedWrapped = f.wrapped.typeMerge f'.wrapped.functor;
88+ mergedPayload = f.binOp f.payload f'.payload;
89+90+ hasPayload = assert (f'.payload != null) == (f.payload != null); f.payload != null;
91+ hasWrapped = assert (f'.wrapped != null) == (f.wrapped != null); f.wrapped != null;
9293+ typeFromPayload = if mergedPayload == null then null else f.type mergedPayload;
94+ typeFromWrapped = if mergedWrapped == null then null else f.type mergedWrapped;
95 in
96 # Abort early: cannot merge different types
97 if f.name != f'.name
···99 else
100101 if hasPayload then
102+ # Just return the payload if returning wrapped is deprecated
103+ if f ? wrappedDeprecationMessage then
104+ typeFromPayload
105+ else if hasWrapped then
106 # Has both wrapped and payload
107 throw ''
108 Type ${f.name} defines both `functor.payload` and `functor.wrapped` at the same time, which is not supported.
109110 Use either `functor.payload` or `functor.wrapped` but not both.
111112+ If your code worked before remove either `functor.wrapped` or `functor.payload` from the type definition.
113 ''
114 else
115+ typeFromPayload
0116 else
117 if hasWrapped then
118+ typeFromWrapped
00119 else
120 f.type;
121···586 substSubModules = m: nonEmptyListOf (elemType.substSubModules m);
587 };
588589+ attrsOf = elemType: attrsWith { inherit elemType; };
00000000000000000590591 # A version of attrsOf that's lazy in its values at the expense of
592 # conditional definitions not working properly. E.g. defining a value with
593 # `foo.attr = mkIf false 10`, then `foo ? attr == true`, whereas with
594 # attrsOf it would correctly be `false`. Accessing `foo.attr` would throw an
595 # error that it's not defined. Use only if conditional definitions don't make sense.
596+ lazyAttrsOf = elemType: attrsWith { inherit elemType; lazy = true; };
597+598+ # base type for lazyAttrsOf and attrsOf
599+ attrsWith =
600+ let
601+ # Push down position info.
602+ pushPositions = map (def: mapAttrs (n: v: { inherit (def) file; value = v; }) def.value);
603+ binOp = lhs: rhs:
604+ let
605+ elemType = lhs.elemType.typeMerge rhs.elemType.functor;
606+ lazy =
607+ if lhs.lazy == rhs.lazy then
608+ lhs.lazy
609+ else
610+ null;
611+ in
612+ if elemType == null || lazy == null then
613+ null
614+ else
615+ {
616+ inherit elemType lazy;
617+ };
618+ in
619+ {
620+ elemType,
621+ lazy ? false,
622+ }:
623+ mkOptionType {
624+ name = if lazy then "lazyAttrsOf" else "attrsOf";
625+ description = (if lazy then "lazy attribute set" else "attribute set") + " of ${optionDescriptionPhrase (class: class == "noun" || class == "composite") elemType}";
626 descriptionClass = "composite";
627 check = isAttrs;
628+ merge = if lazy then (
629+ # Lazy merge Function
630+ loc: defs:
631+ zipAttrsWith (name: defs:
632+ let merged = mergeDefinitions (loc ++ [name]) elemType defs;
633+ # mergedValue will trigger an appropriate error when accessed
634+ in merged.optionalValue.value or elemType.emptyValue.value or merged.mergedValue
635+ )
636+ # Push down position info.
637+ (pushPositions defs)
638+ ) else (
639+ # Non-lazy merge Function
640+ loc: defs:
641+ mapAttrs (n: v: v.value) (filterAttrs (n: v: v ? value) (zipAttrsWith (name: defs:
642+ (mergeDefinitions (loc ++ [name]) elemType (defs)).optionalValue
643+ )
644+ # Push down position info.
645+ (pushPositions defs)))
646+ );
647 emptyValue = { value = {}; };
648 getSubOptions = prefix: elemType.getSubOptions (prefix ++ ["<name>"]);
649 getSubModules = elemType.getSubModules;
650+ substSubModules = m: attrsWith { elemType = elemType.substSubModules m; inherit lazy; };
651+ functor = defaultFunctor "attrsWith" // {
652+ wrappedDeprecationMessage = { loc }: lib.warn ''
653+ The deprecated `type.functor.wrapped` attribute of the option `${showOption loc}` is accessed, use `type.nestedTypes.elemType` instead.
654+ '' elemType;
655+ payload = {
656+ # Important!: Add new function attributes here in case of future changes
657+ inherit elemType lazy;
658+ };
659+ inherit binOp;
660+ };
661 nestedTypes.elemType = elemType;
662 };
663
···5051- `zammad` has had its support for MySQL removed, since it was never working correctly and is now deprecated upstream. Check the [migration guide](https://docs.zammad.org/en/latest/appendix/migrate-to-postgresql.html) for how to convert your database to PostgreSQL.
520053- `kanata` was updated to v1.7.0, which introduces several breaking changes.
54 See the release notes of
55 [v1.7.0](https://github.com/jtroo/kanata/releases/tag/v1.7.0)
···63 suffix and any whitespaces trimmed.
6465- `gkraken` software and `hardware.gkraken.enable` option have been removed, use `coolercontrol` via `programs.coolercontrol.enable` option instead.
00006667- the notmuch vim plugin now lives in a separate output of the `notmuch`
68 package. Installing `notmuch` will not bring the notmuch vim package anymore,
···5051- `zammad` has had its support for MySQL removed, since it was never working correctly and is now deprecated upstream. Check the [migration guide](https://docs.zammad.org/en/latest/appendix/migrate-to-postgresql.html) for how to convert your database to PostgreSQL.
5253+- The behavior of the `networking.nat.externalIP` and `networking.nat.externalIPv6` options has been changed. `networking.nat.forwardPorts` now only forwards packets destined for the specified IP addresses.
54+55- `kanata` was updated to v1.7.0, which introduces several breaking changes.
56 See the release notes of
57 [v1.7.0](https://github.com/jtroo/kanata/releases/tag/v1.7.0)
···65 suffix and any whitespaces trimmed.
6667- `gkraken` software and `hardware.gkraken.enable` option have been removed, use `coolercontrol` via `programs.coolercontrol.enable` option instead.
68+69+- `containerd` has been updated to v2, which contains breaking changes. See the [containerd
70+ 2.0](https://github.com/containerd/containerd/blob/main/docs/containerd-2.0.md) documentation for more
71+ details.
7273- the notmuch vim plugin now lives in a separate output of the `notmuch`
74 package. Installing `notmuch` will not bring the notmuch vim package anymore,
···20 type = types.bool;
21 default = false;
22 description = ''
23- Whether to enable Network Address Translation (NAT).
00024 '';
25 };
26···82 The public IP address to which packets from the local
83 network are to be rewritten. If this is left empty, the
84 IP address associated with the external interface will be
85- used.
086 '';
87 };
88···94 The public IPv6 address to which packets from the local
95 network are to be rewritten. If this is left empty, the
96 IP address associated with the external interface will be
97- used.
098 '';
99 };
100
···20 type = types.bool;
21 default = false;
22 description = ''
23+ Whether to enable Network Address Translation (NAT). A
24+ properly configured firewall or a trusted L2 on all network
25+ interfaces is required to prevent unauthorized access to
26+ the internal network.
27 '';
28 };
29···85 The public IP address to which packets from the local
86 network are to be rewritten. If this is left empty, the
87 IP address associated with the external interface will be
88+ used. Only connections made to this IP address will be
89+ forwarded to the internal network when using forwardPorts.
90 '';
91 };
92···98 The public IPv6 address to which packets from the local
99 network are to be rewritten. If this is left empty, the
100 IP address associated with the external interface will be
101+ used. Only connections made to this IP address will be
102+ forwarded to the internal network when using forwardPorts.
103 '';
104 };
105
···138139 ::: {.note}
140 For available settings, see the SearXNG
141- [schema file](https://github.com/searxng/searxng/blob/master/searx/botdetection/limiter.toml).
142 :::
143 '';
144 };
···138139 ::: {.note}
140 For available settings, see the SearXNG
141+ [schema file](https://github.com/searxng/searxng/blob/master/searx/limiter.toml).
142 :::
143 '';
144 };
···1-# This is a simple distributed test involving a topology with two
2-# separate virtual networks - the "inside" and the "outside" - with a
3-# client on the inside network, a server on the outside network, and a
4-# router connected to both that performs Network Address Translation
5-# for the client.
6-import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ... }:
000000007 let
8 unit = if nftables then "nftables" else (if withFirewall then "firewall" else "nat");
0000000000000000000000000000000000000000000000000000000000000000000000000000009 in
10 {
11 name = "nat" + (lib.optionalString nftables "Nftables")
12 + (if withFirewall then "WithFirewall" else "Standalone");
13 meta = with pkgs.lib.maintainers; {
14- maintainers = [ rob ];
15 };
1617 nodes =
18- {
19- client = { lib, nodes, ... }: {
20- virtualisation.vlans = [ 1 ];
21- networking.defaultGateway =
22- (lib.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address;
23- networking.nftables.enable = nftables;
24- };
00002526- router = { lib, ... }: {
27- virtualisation.vlans = [ 2 1 ];
28- networking.firewall.enable = withFirewall;
29- networking.firewall.filterForward = nftables;
30- networking.nftables.enable = nftables;
31- networking.nat.enable = true;
32- networking.nat.internalIPs = [ "192.168.1.0/24" ];
33- networking.nat.externalInterface = "eth1";
0000000000000003435- specialisation.no-nat.configuration = {
36- networking.nat.enable = lib.mkForce false;
37- };
38- };
000000000000000000003940 server =
41- { ... }:
42- { virtualisation.vlans = [ 2 ];
43- networking.firewall.enable = false;
44- services.httpd.enable = true;
45- services.httpd.adminAddr = "foo@example.org";
46- services.vsftpd.enable = true;
47- services.vsftpd.anonymousUser = true;
48- };
049 };
5051- testScript = ''
00000000052 client.start()
53 router.start()
54 server.start()
5556- # The router should have access to the server.
57- server.wait_for_unit("network.target")
58- server.wait_for_unit("httpd")
59- router.wait_for_unit("network.target")
60- router.succeed("curl -4 --fail http://server/ >&2")
6162- # The client should be also able to connect via the NAT router.
63- router.wait_for_unit("${unit}")
64- client.wait_for_unit("network.target")
65- client.succeed("curl --fail http://server/ >&2")
66- client.succeed("ping -4 -c 1 server >&2")
6768- # Test whether passive FTP works.
69- server.wait_for_unit("vsftpd")
70- server.succeed("echo Hello World > /home/ftp/foo.txt")
71- client.succeed("curl -v ftp://server/foo.txt >&2")
07273- # Test whether active FTP works.
74- client.fail("curl -v -P - ftp://server/foo.txt >&2")
0000000000000000007576- # Test ICMP.
77- client.succeed("ping -4 -c 1 router >&2")
78- router.succeed("ping -4 -c 1 client >&2")
000000007980- # If we turn off NAT, the client shouldn't be able to reach the server.
81 router.succeed(
82- "/run/booted-system/specialisation/no-nat/bin/switch-to-configuration test 2>&1"
83 )
84- client.fail("curl -4 --fail --connect-timeout 5 http://server/ >&2")
85- client.fail("ping -4 -c 1 server >&2")
8687- # And make sure that reloading the NAT job works.
0000000000000000088 router.succeed(
89- "/run/booted-system/bin/switch-to-configuration test 2>&1"
90 )
91- # FIXME: this should not be necessary, but nat.service is not started because
92- # network.target is not triggered
93- # (https://github.com/NixOS/nixpkgs/issues/16230#issuecomment-226408359)
94- ${lib.optionalString (!withFirewall && !nftables) ''
95- router.succeed("systemctl start nat.service")
096 ''}
97- client.succeed("curl -4 --fail http://server/ >&2")
98- client.succeed("ping -4 -c 1 server >&2")
0099 '';
100})
···1+# This is a distributed test of the Network Address Translation involving a topology
2+# with a router inbetween three separate virtual networks:
3+# - "external" -- i.e. the internet,
4+# - "internal" -- i.e. an office LAN,
5+#
6+# This test puts one server on each of those networks and its primary goal is to ensure that:
7+# - server (named client in the code) in internal network can reach server (named server in the code) on the external network,
8+# - server in external network can not reach server in internal network (skipped in some cases),
9+# - when using externalIP, only the specified IP is used for NAT,
10+# - port forwarding functionality behaves correctly
11+#
12+# The client is behind the nat (read: protected by the nat) and the server is on the external network, attempting to access services behind the NAT.
13+14+import ./make-test-python.nix ({ pkgs, lib, withFirewall ? false, nftables ? false, ... }:
15 let
16 unit = if nftables then "nftables" else (if withFirewall then "firewall" else "nat");
17+18+ routerAlternativeExternalIp = "192.168.2.234";
19+20+ makeNginxConfig = hostname: {
21+ enable = true;
22+ virtualHosts."${hostname}" = {
23+ root = "/etc";
24+ locations."/".index = "hostname";
25+ listen = [
26+ {
27+ addr = "0.0.0.0";
28+ port = 80;
29+ }
30+ {
31+ addr = "0.0.0.0";
32+ port = 8080;
33+ }
34+ ];
35+ };
36+ };
37+38+ makeCommonConfig = hostname: {
39+ services.nginx = makeNginxConfig hostname;
40+ services.vsftpd = {
41+ enable = true;
42+ anonymousUser = true;
43+ localRoot = "/etc/";
44+ extraConfig = ''
45+ pasv_min_port=51000
46+ pasv_max_port=51999
47+ '';
48+ };
49+50+ # Disable eth0 autoconfiguration
51+ networking.useDHCP = false;
52+53+ environment.systemPackages = [
54+ (pkgs.writeScriptBin "check-connection"
55+ ''
56+ #!/usr/bin/env bash
57+58+ set -e
59+60+ if [[ "$2" == "" || "$3" == "" || "$1" == "--help" || "$1" == "-h" ]];
61+ then
62+ echo "check-connection <target-address> <target-hostname> <[expect-success|expect-failure]>"
63+ exit 1
64+ fi
65+66+ ADDRESS="$1"
67+ HOSTNAME="$2"
68+69+ function test_icmp() { timeout 3 ping -c 1 $ADDRESS; }
70+ function test_http() { [[ `timeout 3 curl $ADDRESS` == "$HOSTNAME" ]]; }
71+ function test_ftp() { timeout 3 curl ftp://$ADDRESS; }
72+73+ if [[ "$3" == "expect-success" ]];
74+ then
75+ test_icmp; test_http; test_ftp
76+ else
77+ ! test_icmp; ! test_http; ! test_ftp
78+ fi
79+ ''
80+ )
81+ (pkgs.writeScriptBin "check-last-clients-ip"
82+ ''
83+ #!/usr/bin/env bash
84+ set -e
85+86+ [[ `cat /var/log/nginx/access.log | tail -n1 | awk '{print $1}'` == "$1" ]]
87+ ''
88+ )
89+ ];
90+ };
91+92+ # VLANS:
93+ # 1 -- simulates the internal network
94+ # 2 -- simulates the external network
95 in
96 {
97 name = "nat" + (lib.optionalString nftables "Nftables")
98 + (if withFirewall then "WithFirewall" else "Standalone");
99 meta = with pkgs.lib.maintainers; {
100+ maintainers = [ tne rob ];
101 };
102103 nodes =
104+ { client =
105+ { pkgs, nodes, ... }:
106+ lib.mkMerge [
107+ ( makeCommonConfig "client" )
108+ { virtualisation.vlans = [ 1 ];
109+ networking.defaultGateway =
110+ (pkgs.lib.head nodes.router.networking.interfaces.eth1.ipv4.addresses).address;
111+ networking.nftables.enable = nftables;
112+ networking.firewall.enable = false;
113+ }
114+ ];
115116+ router =
117+ { nodes, ... }: lib.mkMerge [
118+ ( makeCommonConfig "router" )
119+ { virtualisation.vlans = [ 1 2 ];
120+ networking.firewall = {
121+ enable = withFirewall;
122+ filterForward = nftables;
123+ allowedTCPPorts = [ 21 80 8080 ];
124+ # For FTP passive mode
125+ allowedTCPPortRanges = [ { from = 51000; to = 51999; } ];
126+ };
127+ networking.nftables.enable = nftables;
128+ networking.nat =
129+ let
130+ clientIp = (pkgs.lib.head nodes.client.networking.interfaces.eth1.ipv4.addresses).address;
131+ serverIp = (pkgs.lib.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address;
132+ in
133+ {
134+ enable = true;
135+ internalIPs = [ "${clientIp}/24" ];
136+ # internalInterfaces = [ "eth1" ];
137+ externalInterface = "eth2";
138+ externalIP = serverIp;
139140+ forwardPorts = [
141+ {
142+ destination = "${clientIp}:8080";
143+ proto = "tcp";
144+ sourcePort = 8080;
145+146+ loopbackIPs = [ serverIp ];
147+ }
148+ ];
149+ };
150+151+ networking.interfaces.eth2.ipv4.addresses =
152+ lib.mkOrder 10000 [ { address = routerAlternativeExternalIp; prefixLength = 24; } ];
153+154+ services.nginx.virtualHosts.router.listen = lib.mkOrder (-1) [ {
155+ addr = routerAlternativeExternalIp;
156+ port = 8080;
157+ } ];
158+159+ specialisation.no-nat.configuration = {
160+ networking.nat.enable = lib.mkForce false;
161+ };
162+ }
163+ ];
164165 server =
166+ { nodes, ... }: lib.mkMerge [
167+ ( makeCommonConfig "server" )
168+ { virtualisation.vlans = [ 2 ];
169+ networking.firewall.enable = false;
170+171+ networking.defaultGateway =
172+ (pkgs.lib.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address;
173+ }
174+ ];
175 };
176177+ testScript =
178+ { nodes, ... }: let
179+ clientIp = (pkgs.lib.head nodes.client.networking.interfaces.eth1.ipv4.addresses).address;
180+ serverIp = (pkgs.lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address;
181+ routerIp = (pkgs.lib.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address;
182+ in ''
183+ def wait_for_machine(m):
184+ m.wait_for_unit("network.target")
185+ m.wait_for_unit("nginx.service")
186+187 client.start()
188 router.start()
189 server.start()
190191+ wait_for_machine(router)
192+ wait_for_machine(client)
193+ wait_for_machine(server)
00194195+ # We assume we are isolated from layer 2 attacks or are securely configured (like disabling forwarding by default)
196+ # Relevant moby issue describing the problem allowing bypassing of NAT: https://github.com/moby/moby/issues/14041
197+ ${lib.optionalString (!nftables) ''
198+ router.succeed("iptables -P FORWARD DROP")
199+ ''}
200201+ # Sanity checks.
202+ ## The router should have direct access to the server
203+ router.succeed("check-connection ${serverIp} server expect-success")
204+ ## The server should have direct access to the router
205+ server.succeed("check-connection ${routerIp} router expect-success")
206207+ # The client should be also able to connect via the NAT router...
208+ client.succeed("check-connection ${serverIp} server expect-success")
209+ # ... but its IP should be rewritten to be that of the router.
210+ server.succeed("check-last-clients-ip ${routerIp}")
211+212+ # Active FTP (where the FTP server connects back to us via a random port) should work directly...
213+ router.succeed("timeout 3 curl -P eth2:51000-51999 ftp://${serverIp}")
214+ # ... but not from behind NAT.
215+ client.fail("timeout 3 curl -P eth1:51000-51999 ftp://${serverIp};")
216+217+ # If using nftables without firewall, filterForward can't be used and L2 security can't easily be simulated like with iptables, skipping.
218+ # See moby github issue mentioned above.
219+ ${lib.optionalString (nftables && withFirewall) ''
220+ # The server should not be able to reach the client directly...
221+ server.succeed("check-connection ${clientIp} client expect-failure")
222+ ''}
223+ # ... but the server should be able to reach a port forwarded address of the client
224+ server.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "client" ]]')
225+ # The IP address the client sees should not be rewritten to be that of the router (#277016)
226+ client.succeed("check-last-clients-ip ${serverIp}")
227228+ # But this forwarded port shouldn't intercept communication with
229+ # other IPs than externalIp.
230+ server.succeed('[[ `timeout 3 curl http://${routerAlternativeExternalIp}:8080` == "router" ]]')
231+232+ # The loopback should allow the router itself to access the forwarded port
233+ # Note: The reason we use routerIp here is because only routerIp is listed for reflection in networking.nat.forwardPorts.loopbackIPs
234+ # The purpose of loopbackIPs is to allow things inside of the NAT to for example access their own public domain when a service has to make a request
235+ # to itself/another service on the same NAT through a public address
236+ router.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "client" ]]')
237+ # The loopback should also allow the client to access its own forwarded port
238+ client.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "client" ]]')
239240+ # If we turn off NAT, nothing should work
241 router.succeed(
242+ "systemctl stop ${unit}.service"
243 )
00244245+ # If using nftables and firewall, this makes no sense. We deactivated the firewall after all,
246+ # so we are once again affected by the same issue as the moby github issue mentioned above.
247+ # If using nftables without firewall, filterForward can't be used and L2 security can't easily be simulated like with iptables, skipping.
248+ # See moby github issue mentioned above.
249+ ${lib.optionalString (!nftables) ''
250+ client.succeed("check-connection ${serverIp} server expect-failure")
251+ server.succeed("check-connection ${clientIp} client expect-failure")
252+ ''}
253+ # These should revert to their pre-NATed versions
254+ server.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "router" ]]')
255+ router.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "router" ]]')
256+257+ # Reverse the effect of nat stop
258+ router.succeed(
259+ "systemctl start ${unit}.service"
260+ )
261+262+ # Switch to a config without NAT at all, again nothing should work
263 router.succeed(
264+ "/run/booted-system/specialisation/no-nat/bin/switch-to-configuration test 2>&1"
265 )
266+267+ # If using nftables without firewall, filterForward can't be used and L2 security can't easily be simulated like with iptables, skipping.
268+ # See moby github issue mentioned above.
269+ ${lib.optionalString (nftables && withFirewall) ''
270+ client.succeed("check-connection ${serverIp} server expect-failure")
271+ server.succeed("check-connection ${clientIp} client expect-failure")
272 ''}
273+274+ # These should revert to their pre-NATed versions
275+ server.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "router" ]]')
276+ router.succeed('[[ `timeout 3 curl http://${routerIp}:8080` == "router" ]]')
277 '';
278})
+13-1
nixos/tests/networking/networkd-and-scripted.nix
···660 assert "02:de:ad:be:ef:01" in machine.succeed("ip link show dev tap0")
661 '' # network-addresses-* only exist in scripted networking
662 + lib.optionalString (!networkd) ''
663- with subtest("Test interfaces clean up"):
664 machine.succeed("systemctl stop network-addresses-tap0")
665 machine.sleep(10)
666 machine.succeed("systemctl stop network-addresses-tun0")
000000000000667 machine.sleep(10)
668 residue = machine.succeed("ip tuntap list")
669 assert (
···660 assert "02:de:ad:be:ef:01" in machine.succeed("ip link show dev tap0")
661 '' # network-addresses-* only exist in scripted networking
662 + lib.optionalString (!networkd) ''
663+ with subtest("Test interfaces' addresses clean up"):
664 machine.succeed("systemctl stop network-addresses-tap0")
665 machine.sleep(10)
666 machine.succeed("systemctl stop network-addresses-tun0")
667+ machine.sleep(10)
668+ residue = machine.succeed("ip tuntap list | sort").strip()
669+ assert (
670+ residue == targetList
671+ ), "Some virtual interface has been removed:\n{}".format(residue)
672+ assert "192.168.1.1" not in machine.succeed("ip address show dev tap0"), "tap0 interface address has not been removed"
673+ assert "192.168.1.2" not in machine.succeed("ip address show dev tun0"), "tun0 interface address has not been removed"
674+675+ with subtest("Test interfaces clean up"):
676+ machine.succeed("systemctl stop tap0-netdev")
677+ machine.sleep(10)
678+ machine.succeed("systemctl stop tun0-netdev")
679 machine.sleep(10)
680 residue = machine.succeed("ip tuntap list")
681 assert (
···1# tests available at pkgs/test/vim
2-{ lib, stdenv, vim, vimPlugins, buildEnv, writeText
3-, runCommand, makeWrapper
4-, python3
5-, callPackage, makeSetupHook
6-, linkFarm
7-, config
000000008}:
910/*
001112-USAGE EXAMPLE
13-=============
1415-Install Vim like this eg using nixos option environment.systemPackages which will provide
16-vim-with-plugins in PATH:
1718- vim-full.customize {
19- name = "vim-with-plugins"; # optional
002021- # add custom .vimrc lines like this:
22- vimrcConfig.customRC = ''
23- set hidden
24- '';
25-26- # store your plugins in Vim packages
27- vimrcConfig.packages.myVimPackage = with pkgs.vimPlugins; {
28- # loaded on launch
29- start = [ youcompleteme fugitive ];
30- # manually loadable by calling `:packadd $plugin-name`
31- opt = [ phpCompletion elm-vim ];
32- # To automatically load a plugin when opening a filetype, add vimrc lines like:
33- # autocmd FileType php :packadd phpCompletion
34 };
35- };
3637-WHAT IS A VIM PLUGIN?
38-=====================
39-Typical plugin files:
4041- plugin/P1.vim
42- autoload/P1.vim
43- ftplugin/xyz.vim
44- doc/plugin-documentation.txt (traditional documentation)
45- README(.md) (nowadays thanks to github)
46004748-Vim offers the :h rtp setting which works for most plugins. Thus adding
49-this to your .vimrc should make most plugins work:
5051- set rtp+=~/.nix-profile/share/vim-plugins/youcompleteme
52- " or for p in ["youcompleteme"] | exec 'set rtp+=~/.nix-profile/share/vim-plugins/'.p | endfor
5354-Learn about about plugin Vim plugin mm managers at
55-http://vim-wiki.mawercer.de/wiki/topic/vim%20plugin%20managment.html.
5657-The documentation can be accessed by Vim's :help command if it was tagged.
58-See vimHelpTags sample code below.
00005960-CONTRIBUTING AND CUSTOMIZING
61-============================
62-The example file pkgs/applications/editors/vim/plugins/default.nix provides
63-both:
64-* manually mantained plugins
65-* plugins created by VAM's nix#ExportPluginsForNix implementation
6667-I highly recommend to lookup vim plugin attribute names at the [vim-pi] project
68- which is a database containing all plugins from
69-vim.org and quite a lot of found at github and similar sources. vim-pi's documented purpose
70-is to associate vim.org script ids to human readable names so that dependencies
71-can be describe easily.
7273-How to find a name?
74- * http://vam.mawercer.de/ or VAM's
75- * grep vim-pi
76- * use VAM's completion or :AddonsInfo command
7778-It might happen than a plugin is not known by vim-pi yet. We encourage you to
79-contribute to vim-pi so that plugins can be updated automatically.
00008008182-CREATING DERIVATIONS AUTOMATICALLY BY PLUGIN NAME
83-==================================================
84-Most convenient is to use a ~/.vim-scripts file putting a plugin name into each line
85-as documented by [VAM]'s README.md
86-It is the same format you pass to vimrcConfig.vam.pluginDictionaries from the
87-usage example above.
08889-Then create a temp vim file and insert:
9091- let opts = {}
92- let opts.path_to_nixpkgs = '/etc/nixos/nixpkgs'
93- let opts.cache_file = '/tmp/export-vim-plugin-for-nix-cache-file'
94- let opts.plugin_dictionaries = map(readfile("vim-plugins"), 'eval(v:val)')
95- " add more files
96- " let opts.plugin_dictionaries += map(.. other file )
97- call nix#ExportPluginsForNix(opts)
9899-Then ":source %" it.
0100101-nix#ExportPluginsForNix is provided by ./vim2nix
102103-A buffer will open containing the plugin derivation lines as well list
104-fitting the vimrcConfig.vam.pluginDictionaries option.
0000000105106-Thus the most simple usage would be:
107108- vim_with_plugins =
109- let vim = vim-full;
110- inherit (vimUtil.override {inherit vim}) rtpPath addRtp buildVimPlugin vimHelpTags;
111- vimPlugins = [
112- # the derivation list from the buffer created by nix#ExportPluginsForNix
113- # don't set which will default to pkgs.vimPlugins
114 ];
115- in vim.customize {
116- name = "vim-with-plugins";
117-118- vimrcConfig.customRC = '' .. '';
119120- vimrcConfig.vam.knownPlugins = vimPlugins;
121- vimrcConfig.vam.pluginDictionaries = [
122- # the plugin list form ~/.vim-scripts turned into nix format added to
123- # the buffer created by the nix#ExportPluginsForNix
124- ];
125- }
126127-vim_with_plugins can be installed like any other application within Nix.
128-129-[VAM] https://github.com/MarcWeber/vim-addon-manager
130-[vim-pi] https://bitbucket.org/vimcommunity/vim-pi
131*/
132-133134let
135 inherit lib;
···137 # make sure a plugin is a derivation and its dependencies are derivations. If
138 # plugin already is a derivation, this is a no-op. If it is a string, it is
139 # looked up in knownPlugins.
140- pluginToDrv = knownPlugins: plugin:
141- let
142- drv =
143- if builtins.isString plugin then
144- # make sure `pname` is set to that we are able to convert the derivation
145- # back to a string.
146- ( knownPlugins.${plugin} // { pname = plugin; })
147- else
148- plugin;
149- in
0150 # make sure all the dependencies of the plugin are also derivations
151- drv // { dependencies = map (pluginToDrv knownPlugins) (drv.dependencies or []); };
152153 # transitive closure of plugin dependencies (plugin needs to be a derivation)
154- transitiveClosure = plugin:
155- [ plugin ] ++ (
156- lib.unique (builtins.concatLists (map transitiveClosure plugin.dependencies or []))
157- );
158159 findDependenciesRecursively = plugins: lib.concatMap transitiveClosure plugins;
160161- vamDictToNames = x:
162- if builtins.isString x then [x]
163- else (lib.optional (x ? name) x.name)
164- ++ (x.names or []);
165166 rtpPath = ".";
167168- vimFarm = prefix: name: drvs:
169- let mkEntryFromDrv = drv: { name = "${prefix}/${lib.getName drv}"; path = drv; };
170- in linkFarm name (map mkEntryFromDrv drvs);
000000171172- /* Generates a packpath folder as expected by vim
173- Example:
174- packDir (myVimPackage.{ start = [ vimPlugins.vim-fugitive ]; opt = [] })
175- => "/nix/store/xxxxx-pack-dir"
0176 */
177- packDir = packages:
178- let
179- packageLinks = packageName: {start ? [], opt ? []}:
180 let
181- # `nativeImpl` expects packages to be derivations, not strings (as
182- # opposed to older implementations that have to maintain backwards
183- # compatibility). Therefore we don't need to deal with "knownPlugins"
184- # and can simply pass `null`.
185- depsOfOptionalPlugins = lib.subtractLists opt (findDependenciesRecursively opt);
186- startWithDeps = findDependenciesRecursively start;
187- allPlugins = lib.unique (startWithDeps ++ depsOfOptionalPlugins);
188- allPython3Dependencies = ps:
189- lib.flatten (builtins.map (plugin: (plugin.python3Dependencies or (_: [])) ps) allPlugins);
190- python3Env = python3.withPackages allPython3Dependencies;
0000000191192- packdirStart = vimFarm "pack/${packageName}/start" "packdir-start" allPlugins;
193- packdirOpt = vimFarm "pack/${packageName}/opt" "packdir-opt" opt;
194- # Assemble all python3 dependencies into a single `site-packages` to avoid doing recursive dependency collection
195- # for each plugin.
196- # This directory is only for python import search path, and will not slow down the startup time.
197- # see :help python3-directory for more details
198- python3link = runCommand "vim-python3-deps" {} ''
199- mkdir -p $out/pack/${packageName}/start/__python3_dependencies
200- ln -s ${python3Env}/${python3Env.sitePackages} $out/pack/${packageName}/start/__python3_dependencies/python3
201- '';
000000202 in
203- [ packdirStart packdirOpt ] ++ lib.optional (allPython3Dependencies python3.pkgs != []) python3link;
204- in
205 buildEnv {
206 name = "vim-pack-dir";
207 paths = (lib.flatten (lib.mapAttrsToList packageLinks packages));
208 };
209210- nativeImpl = packages:
211- ''
212 set packpath^=${packDir packages}
213 set runtimepath^=${packDir packages}
214 '';
215216- /* Generates a vimrc string
0217218 packages is an attrset with {name: { start = [ vim derivations ]; opt = [ vim derivations ]; }
219 Example:
···224 customRC = ''let mapleader = " "'';
225226 };
227- */
228- vimrcContent = {
229- packages ? null,
230- vam ? null, # deprecated
231- pathogen ? null, # deprecated
232- plug ? null,
233- beforePlugins ? ''
234- " configuration generated by NIX
235- set nocompatible
236- '',
237- customRC ? null
238- }:
0239240 let
241- /* vim-plug is an extremely popular vim plugin manager.
242- */
243 plugImpl =
244- ''
245- source ${vimPlugins.vim-plug}/plug.vim
246- silent! call plug#begin('/dev/null')
247248- '' + (lib.concatMapStringsSep "\n" (pkg: "Plug '${pkg}'") plug.plugins) + ''
00249250- call plug#end()
251- '';
252253- # vim-addon-manager = VAM (deprecated)
254 vamImpl =
255- let
256- knownPlugins = vam.knownPlugins or vimPlugins;
257258- # plugins specified by the user
259- specifiedPlugins = map (pluginToDrv knownPlugins) (lib.concatMap vamDictToNames vam.pluginDictionaries);
260- # plugins with dependencies
261- plugins = findDependenciesRecursively specifiedPlugins;
262- vamPackages.vam = {
263- start = plugins;
264- };
265- in
00266 nativeImpl vamPackages;
267268- entries = [
269- beforePlugins
270- ]
271- ++ lib.optional (vam != null) (lib.warn "'vam' attribute is deprecated. Use 'packages' instead in your vim configuration" vamImpl)
272- ++ lib.optional (packages != null && packages != []) (nativeImpl packages)
273- ++ lib.optional (pathogen != null) (throw "pathogen is now unsupported, replace `pathogen = {}` with `packages.home = { start = []; }`")
274- ++ lib.optional (plug != null) plugImpl
275- ++ [ customRC ];
00000276277 in
278- lib.concatStringsSep "\n" (lib.filter (x: x != null && x != "") entries);
279280 vimrcFile = settings: writeText "vimrc" (vimrcContent settings);
281···286 inherit vimrcContent;
287 inherit packDir;
288289- makeCustomizable = let
290- mkVimrcFile = vimrcFile; # avoid conflict with argument name
291- in vim: vim // {
292- # Returns a customized vim that uses the specified vimrc configuration.
293- customize =
294- { # The name of the derivation.
295- name ? "vim"
296- , # A shell word used to specify the names of the customized executables.
297- # The shell variable $exe can be used to refer to the wrapped executable's name.
298- # Examples: "my-$exe", "$exe-with-plugins", "\${exe/vim/v1m}"
299- executableName ?
300- if lib.hasInfix "vim" name then
301- lib.replaceStrings [ "vim" ] [ "$exe" ] name
302- else
303- "\${exe/vim/${lib.escapeShellArg name}}"
304- , # A custom vimrc configuration, treated as an argument to vimrcContent (see the documentation in this file).
305- vimrcConfig ? null
306- , # A custom vimrc file.
307- vimrcFile ? null
308- , # A custom gvimrc file.
309- gvimrcFile ? null
310- , # If set to true, return the *vim wrappers only.
311- # If set to false, overlay the wrappers on top of the original vim derivation.
312- # This ensures that things like man pages and .desktop files are available.
313- standalone ? name != "vim" && wrapManual != true
00000314315- , # deprecated arguments (TODO: remove eventually)
316- wrapManual ? null, wrapGui ? null, vimExecutableName ? null, gvimExecutableName ? null,
317- }:
318- lib.warnIf (wrapManual != null) ''
319- vim.customize: wrapManual is deprecated: the manual is now included by default if `name == "vim"`.
320- ${if wrapManual == true && name != "vim" then "Set `standalone = false` to include the manual."
321- else lib.optionalString (wrapManual == false && name == "vim") "Set `standalone = true` to get the *vim wrappers only."
322- }''
323- lib.warnIf (wrapGui != null)
324- "vim.customize: wrapGui is deprecated: gvim is now automatically included if present"
325- lib.throwIfNot (vimExecutableName == null && gvimExecutableName == null)
326- "vim.customize: (g)vimExecutableName is deprecated: use executableName instead (see source code for examples)"
327- (let
328- vimrc =
329- if vimrcFile != null then vimrcFile
330- else if vimrcConfig != null then mkVimrcFile vimrcConfig
331- else throw "at least one of vimrcConfig and vimrcFile must be specified";
332- bin = runCommand "${name}-bin" { nativeBuildInputs = [ makeWrapper ]; } ''
333- vimrc=${lib.escapeShellArg vimrc}
334- gvimrc=${lib.optionalString (gvimrcFile != null) (lib.escapeShellArg gvimrcFile)}
000000000000000335336- mkdir -p "$out/bin"
337- for exe in ${
338- if standalone then "{,g,r,rg,e}vim {,g}vimdiff vi"
339- else "{,g,r,rg,e}{vim,view} {,g}vimdiff ex vi"
340- }; do
341- if [[ -e ${vim}/bin/$exe ]]; then
342- dest="$out/bin/${executableName}"
343- if [[ -e $dest ]]; then
344- echo "ambiguous executableName: ''${dest##*/} already exists"
345- continue
346- fi
347- makeWrapper ${vim}/bin/"$exe" "$dest" \
348- --add-flags "-u ''${vimrc@Q} ''${gvimrc:+-U ''${gvimrc@Q}}"
349- fi
350- done
351- '';
352- in if standalone then bin else
353- buildEnv {
354- inherit name;
355- paths = [ (lib.lowPrio vim) bin ];
356- });
000000357358- override = f: makeCustomizable (vim.override f);
359- overrideAttrs = f: makeCustomizable (vim.overrideAttrs f);
360- };
361362- vimGenDocHook = callPackage ({ vim }:
0363 makeSetupHook {
364 name = "vim-gen-doc-hook";
365 propagatedBuildInputs = [ vim ];
···367 vimBinary = "${vim}/bin/vim";
368 inherit rtpPath;
369 };
370- } ./vim-gen-doc-hook.sh) {};
0371372- vimCommandCheckHook = callPackage ({ neovim-unwrapped }:
0373 makeSetupHook {
374 name = "vim-command-check-hook";
375 propagatedBuildInputs = [ neovim-unwrapped ];
···377 vimBinary = "${neovim-unwrapped}/bin/nvim";
378 inherit rtpPath;
379 };
380- } ./vim-command-check-hook.sh) {};
0381382- neovimRequireCheckHook = callPackage ({ neovim-unwrapped }:
0383 makeSetupHook {
384 name = "neovim-require-check-hook";
385 propagatedBuildInputs = [ neovim-unwrapped ];
···387 nvimBinary = "${neovim-unwrapped}/bin/nvim";
388 inherit rtpPath;
389 };
390- } ./neovim-require-check-hook.sh) {};
0391392- inherit (import ./build-vim-plugin.nix {
393- inherit lib stdenv rtpPath toVimPlugin;
394- }) buildVimPlugin;
00000000395396 buildVimPluginFrom2Nix = lib.warn "buildVimPluginFrom2Nix is deprecated: use buildVimPlugin instead" buildVimPlugin;
397398 # used to figure out which python dependencies etc. neovim needs
399- requiredPlugins = {
400- packages ? {},
401- plug ? null, ...
402- }:
00403 let
404 nativePluginsConfigs = lib.attrsets.attrValues packages;
405 nonNativePlugins = (lib.optionals (plug != null) plug.plugins);
406 nativePlugins = lib.concatMap (requiredPluginsForPackage) nativePluginsConfigs;
407 in
408- nativePlugins ++ nonNativePlugins;
409-410411 # figures out which python dependencies etc. is needed for one vim package
412- requiredPluginsForPackage = { start ? [], opt ? []}:
0000413 start ++ opt;
414415- toVimPlugin = drv:
416- drv.overrideAttrs(oldAttrs: {
0417 name = "vimplugin-${oldAttrs.name}";
418 # dont move the "doc" folder since vim expects it
419- forceShare = [ "man" "info" ];
000420421- nativeBuildInputs = oldAttrs.nativeBuildInputs or []
422- ++ lib.optionals (stdenv.buildPlatform.canExecute stdenv.hostPlatform) [
423- vimCommandCheckHook vimGenDocHook
424- # many neovim plugins keep using buildVimPlugin
425- neovimRequireCheckHook
426- ];
00427428- passthru = (oldAttrs.passthru or {}) // {
429 vimPlugin = true;
430 };
431 });
432-} // lib.optionalAttrs config.allowAliases {
0433 vimWithRC = throw "vimWithRC was removed, please use vim.customize instead";
434}
···1# tests available at pkgs/test/vim
2+{
3+ lib,
4+ stdenv,
5+ vim,
6+ vimPlugins,
7+ buildEnv,
8+ writeText,
9+ runCommand,
10+ makeWrapper,
11+ python3,
12+ callPackage,
13+ makeSetupHook,
14+ linkFarm,
15+ config,
16}:
1718/*
19+ USAGE EXAMPLE
20+ =============
2122+ Install Vim like this eg using nixos option environment.systemPackages which will provide
23+ vim-with-plugins in PATH:
2425+ vim-full.customize {
26+ name = "vim-with-plugins"; # optional
2728+ # add custom .vimrc lines like this:
29+ vimrcConfig.customRC = ''
30+ set hidden
31+ '';
3233+ # store your plugins in Vim packages
34+ vimrcConfig.packages.myVimPackage = with pkgs.vimPlugins; {
35+ # loaded on launch
36+ start = [ youcompleteme fugitive ];
37+ # manually loadable by calling `:packadd $plugin-name`
38+ opt = [ phpCompletion elm-vim ];
39+ # To automatically load a plugin when opening a filetype, add vimrc lines like:
40+ # autocmd FileType php :packadd phpCompletion
41+ };
000042 };
04344+ WHAT IS A VIM PLUGIN?
45+ =====================
46+ Typical plugin files:
4748+ plugin/P1.vim
49+ autoload/P1.vim
50+ ftplugin/xyz.vim
51+ doc/plugin-documentation.txt (traditional documentation)
52+ README(.md) (nowadays thanks to github)
5354+ Vim offers the :h rtp setting which works for most plugins. Thus adding
55+ this to your .vimrc should make most plugins work:
5657+ set rtp+=~/.nix-profile/share/vim-plugins/youcompleteme
58+ " or for p in ["youcompleteme"] | exec 'set rtp+=~/.nix-profile/share/vim-plugins/'.p | endfor
5960+ Learn about about plugin Vim plugin mm managers at
61+ http://vim-wiki.mawercer.de/wiki/topic/vim%20plugin%20managment.html.
6263+ The documentation can be accessed by Vim's :help command if it was tagged.
64+ See vimHelpTags sample code below.
6566+ CONTRIBUTING AND CUSTOMIZING
67+ ============================
68+ The example file pkgs/applications/editors/vim/plugins/default.nix provides
69+ both:
70+ * manually mantained plugins
71+ * plugins created by VAM's nix#ExportPluginsForNix implementation
7273+ I highly recommend to lookup vim plugin attribute names at the [vim-pi] project
74+ which is a database containing all plugins from
75+ vim.org and quite a lot of found at github and similar sources. vim-pi's documented purpose
76+ is to associate vim.org script ids to human readable names so that dependencies
77+ can be describe easily.
07879+ How to find a name?
80+ * http://vam.mawercer.de/ or VAM's
81+ * grep vim-pi
82+ * use VAM's completion or :AddonsInfo command
08384+ It might happen than a plugin is not known by vim-pi yet. We encourage you to
85+ contribute to vim-pi so that plugins can be updated automatically.
008687+ CREATING DERIVATIONS AUTOMATICALLY BY PLUGIN NAME
88+ ==================================================
89+ Most convenient is to use a ~/.vim-scripts file putting a plugin name into each line
90+ as documented by [VAM]'s README.md
91+ It is the same format you pass to vimrcConfig.vam.pluginDictionaries from the
92+ usage example above.
9394+ Then create a temp vim file and insert:
9596+ let opts = {}
97+ let opts.path_to_nixpkgs = '/etc/nixos/nixpkgs'
98+ let opts.cache_file = '/tmp/export-vim-plugin-for-nix-cache-file'
99+ let opts.plugin_dictionaries = map(readfile("vim-plugins"), 'eval(v:val)')
100+ " add more files
101+ " let opts.plugin_dictionaries += map(.. other file )
102+ call nix#ExportPluginsForNix(opts)
103104+ Then ":source %" it.
105106+ nix#ExportPluginsForNix is provided by ./vim2nix
000000107108+ A buffer will open containing the plugin derivation lines as well list
109+ fitting the vimrcConfig.vam.pluginDictionaries option.
110111+ Thus the most simple usage would be:
112113+ vim_with_plugins =
114+ let vim = vim-full;
115+ inherit (vimUtil.override {inherit vim}) rtpPath addRtp buildVimPlugin vimHelpTags;
116+ vimPlugins = [
117+ # the derivation list from the buffer created by nix#ExportPluginsForNix
118+ # don't set which will default to pkgs.vimPlugins
119+ ];
120+ in vim.customize {
121+ name = "vim-with-plugins";
122123+ vimrcConfig.customRC = '' .. '';
124125+ vimrcConfig.vam.knownPlugins = vimPlugins;
126+ vimrcConfig.vam.pluginDictionaries = [
127+ # the plugin list form ~/.vim-scripts turned into nix format added to
128+ # the buffer created by the nix#ExportPluginsForNix
00129 ];
130+ }
000131132+ vim_with_plugins can be installed like any other application within Nix.
00000133134+ [VAM] https://github.com/MarcWeber/vim-addon-manager
135+ [vim-pi] https://bitbucket.org/vimcommunity/vim-pi
00136*/
0137138let
139 inherit lib;
···141 # make sure a plugin is a derivation and its dependencies are derivations. If
142 # plugin already is a derivation, this is a no-op. If it is a string, it is
143 # looked up in knownPlugins.
144+ pluginToDrv =
145+ knownPlugins: plugin:
146+ let
147+ drv =
148+ if builtins.isString plugin then
149+ # make sure `pname` is set to that we are able to convert the derivation
150+ # back to a string.
151+ (knownPlugins.${plugin} // { pname = plugin; })
152+ else
153+ plugin;
154+ in
155 # make sure all the dependencies of the plugin are also derivations
156+ drv // { dependencies = map (pluginToDrv knownPlugins) (drv.dependencies or [ ]); };
157158 # transitive closure of plugin dependencies (plugin needs to be a derivation)
159+ transitiveClosure =
160+ plugin:
161+ [ plugin ]
162+ ++ (lib.unique (builtins.concatLists (map transitiveClosure plugin.dependencies or [ ])));
163164 findDependenciesRecursively = plugins: lib.concatMap transitiveClosure plugins;
165166+ vamDictToNames =
167+ x: if builtins.isString x then [ x ] else (lib.optional (x ? name) x.name) ++ (x.names or [ ]);
00168169 rtpPath = ".";
170171+ vimFarm =
172+ prefix: name: drvs:
173+ let
174+ mkEntryFromDrv = drv: {
175+ name = "${prefix}/${lib.getName drv}";
176+ path = drv;
177+ };
178+ in
179+ linkFarm name (map mkEntryFromDrv drvs);
180181+ /*
182+ Generates a packpath folder as expected by vim
183+ Example:
184+ packDir (myVimPackage.{ start = [ vimPlugins.vim-fugitive ]; opt = [] })
185+ => "/nix/store/xxxxx-pack-dir"
186 */
187+ packDir =
188+ packages:
0189 let
190+ packageLinks =
191+ packageName:
192+ {
193+ start ? [ ],
194+ opt ? [ ],
195+ }:
196+ let
197+ # `nativeImpl` expects packages to be derivations, not strings (as
198+ # opposed to older implementations that have to maintain backwards
199+ # compatibility). Therefore we don't need to deal with "knownPlugins"
200+ # and can simply pass `null`.
201+ depsOfOptionalPlugins = lib.subtractLists opt (findDependenciesRecursively opt);
202+ startWithDeps = findDependenciesRecursively start;
203+ allPlugins = lib.unique (startWithDeps ++ depsOfOptionalPlugins);
204+ allPython3Dependencies =
205+ ps: lib.flatten (builtins.map (plugin: (plugin.python3Dependencies or (_: [ ])) ps) allPlugins);
206+ python3Env = python3.withPackages allPython3Dependencies;
207208+ packdirStart = vimFarm "pack/${packageName}/start" "packdir-start" allPlugins;
209+ packdirOpt = vimFarm "pack/${packageName}/opt" "packdir-opt" opt;
210+ # Assemble all python3 dependencies into a single `site-packages` to avoid doing recursive dependency collection
211+ # for each plugin.
212+ # This directory is only for python import search path, and will not slow down the startup time.
213+ # see :help python3-directory for more details
214+ python3link = runCommand "vim-python3-deps" { } ''
215+ mkdir -p $out/pack/${packageName}/start/__python3_dependencies
216+ ln -s ${python3Env}/${python3Env.sitePackages} $out/pack/${packageName}/start/__python3_dependencies/python3
217+ '';
218+ in
219+ [
220+ packdirStart
221+ packdirOpt
222+ ]
223+ ++ lib.optional (allPython3Dependencies python3.pkgs != [ ]) python3link;
224 in
00225 buildEnv {
226 name = "vim-pack-dir";
227 paths = (lib.flatten (lib.mapAttrsToList packageLinks packages));
228 };
229230+ nativeImpl = packages: ''
0231 set packpath^=${packDir packages}
232 set runtimepath^=${packDir packages}
233 '';
234235+ /*
236+ Generates a vimrc string
237238 packages is an attrset with {name: { start = [ vim derivations ]; opt = [ vim derivations ]; }
239 Example:
···244 customRC = ''let mapleader = " "'';
245246 };
247+ */
248+ vimrcContent =
249+ {
250+ packages ? null,
251+ vam ? null, # deprecated
252+ pathogen ? null, # deprecated
253+ plug ? null,
254+ beforePlugins ? ''
255+ " configuration generated by NIX
256+ set nocompatible
257+ '',
258+ customRC ? null,
259+ }:
260261 let
262+ # vim-plug is an extremely popular vim plugin manager.
0263 plugImpl =
264+ ''
265+ source ${vimPlugins.vim-plug}/plug.vim
266+ silent! call plug#begin('/dev/null')
267268+ ''
269+ + (lib.concatMapStringsSep "\n" (pkg: "Plug '${pkg}'") plug.plugins)
270+ + ''
271272+ call plug#end()
273+ '';
274275+ # vim-addon-manager = VAM (deprecated)
276 vamImpl =
277+ let
278+ knownPlugins = vam.knownPlugins or vimPlugins;
279280+ # plugins specified by the user
281+ specifiedPlugins = map (pluginToDrv knownPlugins) (
282+ lib.concatMap vamDictToNames vam.pluginDictionaries
283+ );
284+ # plugins with dependencies
285+ plugins = findDependenciesRecursively specifiedPlugins;
286+ vamPackages.vam = {
287+ start = plugins;
288+ };
289+ in
290 nativeImpl vamPackages;
291292+ entries =
293+ [
294+ beforePlugins
295+ ]
296+ ++ lib.optional (vam != null) (
297+ lib.warn "'vam' attribute is deprecated. Use 'packages' instead in your vim configuration" vamImpl
298+ )
299+ ++ lib.optional (packages != null && packages != [ ]) (nativeImpl packages)
300+ ++ lib.optional (pathogen != null) (
301+ throw "pathogen is now unsupported, replace `pathogen = {}` with `packages.home = { start = []; }`"
302+ )
303+ ++ lib.optional (plug != null) plugImpl
304+ ++ [ customRC ];
305306 in
307+ lib.concatStringsSep "\n" (lib.filter (x: x != null && x != "") entries);
308309 vimrcFile = settings: writeText "vimrc" (vimrcContent settings);
310···315 inherit vimrcContent;
316 inherit packDir;
317318+ makeCustomizable =
319+ let
320+ mkVimrcFile = vimrcFile; # avoid conflict with argument name
321+ in
322+ vim:
323+ vim
324+ // {
325+ # Returns a customized vim that uses the specified vimrc configuration.
326+ customize =
327+ {
328+ # The name of the derivation.
329+ name ? "vim",
330+ # A shell word used to specify the names of the customized executables.
331+ # The shell variable $exe can be used to refer to the wrapped executable's name.
332+ # Examples: "my-$exe", "$exe-with-plugins", "\${exe/vim/v1m}"
333+ executableName ?
334+ if lib.hasInfix "vim" name then
335+ lib.replaceStrings [ "vim" ] [ "$exe" ] name
336+ else
337+ "\${exe/vim/${lib.escapeShellArg name}}",
338+ # A custom vimrc configuration, treated as an argument to vimrcContent (see the documentation in this file).
339+ vimrcConfig ? null,
340+ # A custom vimrc file.
341+ vimrcFile ? null,
342+ # A custom gvimrc file.
343+ gvimrcFile ? null,
344+ # If set to true, return the *vim wrappers only.
345+ # If set to false, overlay the wrappers on top of the original vim derivation.
346+ # This ensures that things like man pages and .desktop files are available.
347+ standalone ? name != "vim" && wrapManual != true,
348349+ # deprecated arguments (TODO: remove eventually)
350+ wrapManual ? null,
351+ wrapGui ? null,
352+ vimExecutableName ? null,
353+ gvimExecutableName ? null,
354+ }:
355+ lib.warnIf (wrapManual != null)
356+ ''
357+ vim.customize: wrapManual is deprecated: the manual is now included by default if `name == "vim"`.
358+ ${
359+ if wrapManual == true && name != "vim" then
360+ "Set `standalone = false` to include the manual."
361+ else
362+ lib.optionalString (
363+ wrapManual == false && name == "vim"
364+ ) "Set `standalone = true` to get the *vim wrappers only."
365+ }''
366+ lib.warnIf
367+ (wrapGui != null)
368+ "vim.customize: wrapGui is deprecated: gvim is now automatically included if present"
369+ lib.throwIfNot
370+ (vimExecutableName == null && gvimExecutableName == null)
371+ "vim.customize: (g)vimExecutableName is deprecated: use executableName instead (see source code for examples)"
372+ (
373+ let
374+ vimrc =
375+ if vimrcFile != null then
376+ vimrcFile
377+ else if vimrcConfig != null then
378+ mkVimrcFile vimrcConfig
379+ else
380+ throw "at least one of vimrcConfig and vimrcFile must be specified";
381+ bin = runCommand "${name}-bin" { nativeBuildInputs = [ makeWrapper ]; } ''
382+ vimrc=${lib.escapeShellArg vimrc}
383+ gvimrc=${lib.optionalString (gvimrcFile != null) (lib.escapeShellArg gvimrcFile)}
384385+ mkdir -p "$out/bin"
386+ for exe in ${
387+ if standalone then "{,g,r,rg,e}vim {,g}vimdiff vi" else "{,g,r,rg,e}{vim,view} {,g}vimdiff ex vi"
388+ }; do
389+ if [[ -e ${vim}/bin/$exe ]]; then
390+ dest="$out/bin/${executableName}"
391+ if [[ -e $dest ]]; then
392+ echo "ambiguous executableName: ''${dest##*/} already exists"
393+ continue
394+ fi
395+ makeWrapper ${vim}/bin/"$exe" "$dest" \
396+ --add-flags "-u ''${vimrc@Q} ''${gvimrc:+-U ''${gvimrc@Q}}"
397+ fi
398+ done
399+ '';
400+ in
401+ if standalone then
402+ bin
403+ else
404+ buildEnv {
405+ inherit name;
406+ paths = [
407+ (lib.lowPrio vim)
408+ bin
409+ ];
410+ }
411+ );
412413+ override = f: makeCustomizable (vim.override f);
414+ overrideAttrs = f: makeCustomizable (vim.overrideAttrs f);
415+ };
416417+ vimGenDocHook = callPackage (
418+ { vim }:
419 makeSetupHook {
420 name = "vim-gen-doc-hook";
421 propagatedBuildInputs = [ vim ];
···423 vimBinary = "${vim}/bin/vim";
424 inherit rtpPath;
425 };
426+ } ./vim-gen-doc-hook.sh
427+ ) { };
428429+ vimCommandCheckHook = callPackage (
430+ { neovim-unwrapped }:
431 makeSetupHook {
432 name = "vim-command-check-hook";
433 propagatedBuildInputs = [ neovim-unwrapped ];
···435 vimBinary = "${neovim-unwrapped}/bin/nvim";
436 inherit rtpPath;
437 };
438+ } ./vim-command-check-hook.sh
439+ ) { };
440441+ neovimRequireCheckHook = callPackage (
442+ { neovim-unwrapped }:
443 makeSetupHook {
444 name = "neovim-require-check-hook";
445 propagatedBuildInputs = [ neovim-unwrapped ];
···447 nvimBinary = "${neovim-unwrapped}/bin/nvim";
448 inherit rtpPath;
449 };
450+ } ./neovim-require-check-hook.sh
451+ ) { };
452453+ inherit
454+ (import ./build-vim-plugin.nix {
455+ inherit
456+ lib
457+ stdenv
458+ rtpPath
459+ toVimPlugin
460+ ;
461+ })
462+ buildVimPlugin
463+ ;
464465 buildVimPluginFrom2Nix = lib.warn "buildVimPluginFrom2Nix is deprecated: use buildVimPlugin instead" buildVimPlugin;
466467 # used to figure out which python dependencies etc. neovim needs
468+ requiredPlugins =
469+ {
470+ packages ? { },
471+ plug ? null,
472+ ...
473+ }:
474 let
475 nativePluginsConfigs = lib.attrsets.attrValues packages;
476 nonNativePlugins = (lib.optionals (plug != null) plug.plugins);
477 nativePlugins = lib.concatMap (requiredPluginsForPackage) nativePluginsConfigs;
478 in
479+ nativePlugins ++ nonNativePlugins;
0480481 # figures out which python dependencies etc. is needed for one vim package
482+ requiredPluginsForPackage =
483+ {
484+ start ? [ ],
485+ opt ? [ ],
486+ }:
487 start ++ opt;
488489+ toVimPlugin =
490+ drv:
491+ drv.overrideAttrs (oldAttrs: {
492 name = "vimplugin-${oldAttrs.name}";
493 # dont move the "doc" folder since vim expects it
494+ forceShare = [
495+ "man"
496+ "info"
497+ ];
498499+ nativeBuildInputs =
500+ oldAttrs.nativeBuildInputs or [ ]
501+ ++ lib.optionals (stdenv.buildPlatform.canExecute stdenv.hostPlatform) [
502+ vimCommandCheckHook
503+ vimGenDocHook
504+ # many neovim plugins keep using buildVimPlugin
505+ neovimRequireCheckHook
506+ ];
507508+ passthru = (oldAttrs.passthru or { }) // {
509 vimPlugin = true;
510 };
511 });
512+}
513+// lib.optionalAttrs config.allowAliases {
514 vimWithRC = throw "vimWithRC was removed, please use vim.customize instead";
515}
···1{ lib, buildGoModule, fetchFromGitHub }:
2buildGoModule rec {
3 pname = "protolint";
4- version = "0.50.5";
56 src = fetchFromGitHub {
7 owner = "yoheimuta";
8 repo = pname;
9 rev = "v${version}";
10- hash = "sha256-dJurnM+AXdAd0/WBfnGT8KfpLmKHd5YAIZvMj5HHibI=";
11 };
1213- vendorHash = "sha256-pjDVOD6McJdER+BbUckKt4WW/AXsCxdA2nNn8iWSlGE=";
1415 # Something about the way we run tests causes issues. It doesn't happen
16 # when using "go test" directly:
···1{ lib, buildGoModule, fetchFromGitHub }:
2buildGoModule rec {
3 pname = "protolint";
4+ version = "0.51.0";
56 src = fetchFromGitHub {
7 owner = "yoheimuta";
8 repo = pname;
9 rev = "v${version}";
10+ hash = "sha256-q5Ck9p4UXmOur4mtQZ8nbBeXKCi5F++N5JU+E+sSHFk=";
11 };
1213+ vendorHash = "sha256-u4KLYzM1A3t7qdIdbOw6rYPIBnO7TXKjxSgSUNB+Pcg=";
1415 # Something about the way we run tests causes issues. It doesn't happen
16 # when using "go test" directly:
+7-1
pkgs/by-name/pr/proton-ge-bin/package.nix
···25 # Also leave some breadcrumbs in the file.
26 echo "${finalAttrs.pname} should not be installed into environments. Please use programs.steam.extraCompatPackages instead." > $out
2728- ln -s $src $steamcompattool
0000002930 runHook postBuild
31 '';
···25 # Also leave some breadcrumbs in the file.
26 echo "${finalAttrs.pname} should not be installed into environments. Please use programs.steam.extraCompatPackages instead." > $out
2728+ mkdir $steamcompattool
29+ ln -s $src/* $steamcompattool
30+ rm $steamcompattool/{compatibilitytool.vdf,proton,version}
31+ cp $src/{compatibilitytool.vdf,proton,version} $steamcompattool
32+33+ sed -i -r 's|GE-Proton[0-9]*-[0-9]*|GE-Proton|' $steamcompattool/compatibilitytool.vdf
34+ sed -i -r 's|GE-Proton[0-9]*-[0-9]*|GE-Proton|' $steamcompattool/proton
3536 runHook postBuild
37 '';
···1{ lib
2, stdenv
3, substituteAll
04, fetchFromGitLab
5, buildGoModule
6, wrapQtAppsHook
07, python3Packages
8, pkg-config
9, openvpn
···30 owner = "leap";
31 repo = "bitmask-vpn";
32 rev = "8b3ac473f64b6de0262fbf945ff25af8029134f1";
33- sha256 = "sha256-nYMfO091w6H7LyY1+aYubFppg4/3GiZZm4e+0m9Gb3k=";
034 };
3536 # bitmask-root is only used on GNU/Linux
···105106 nativeBuildInputs = [
107 cmake
0108 pkg-config
0109 python3Packages.wrapPython
110 which
111 wrapQtAppsHook
···130 # gui/build.sh will build Go modules into lib/libgoshim.a
131 buildPhase = ''
132 runHook preBuild
00133134 # TODO: this is a hack that copies the qrc file that should by built by qmlcachegen
135 # qmlcachegen is in qtdeclarative/libexec, but qmake is in qtbase/bin
···1{ lib
2, stdenv
3, substituteAll
4+, git
5, fetchFromGitLab
6, buildGoModule
7, wrapQtAppsHook
8+, python3
9, python3Packages
10, pkg-config
11, openvpn
···32 owner = "leap";
33 repo = "bitmask-vpn";
34 rev = "8b3ac473f64b6de0262fbf945ff25af8029134f1";
35+ leaveDotGit = true;
36+ sha256 = "sha256-XUgCVHnTLZXFU+r0s1yuYryWNBJRgQrFlf3g1iRrLWs=";
37 };
3839 # bitmask-root is only used on GNU/Linux
···108109 nativeBuildInputs = [
110 cmake
111+ git
112 pkg-config
113+ python3
114 python3Packages.wrapPython
115 which
116 wrapQtAppsHook
···135 # gui/build.sh will build Go modules into lib/libgoshim.a
136 buildPhase = ''
137 runHook preBuild
138+139+ make vendor
140141 # TODO: this is a hack that copies the qrc file that should by built by qmlcachegen
142 # qmlcachegen is in qtdeclarative/libexec, but qmake is in qtbase/bin
+1-7
pkgs/tools/package-management/nix/default.nix
···172 enableParallelChecking = false;
173 };
174175- nix_2_18 = common {
176- version = "2.18.9";
177- hash = "sha256-RrOFlDGmRXcVRV2p2HqHGqvzGNyWoD0Dado/BNlJ1SI=";
178- self_attribute_name = "nix_2_18";
179- };
180-181 nix_2_24 = common {
182 version = "2.24.10";
183 hash = "sha256-XdeVy1/d6DEIYb3nOA6JIYF4fwMKNxtwJMgT3pHi+ko=";
···230 attr = "nix_2_${toString minor}";
231 in
232 lib.nameValuePair attr (throw "${attr} has been removed")
233- ) (lib.range 4 17))
234 // {
235 unstable = throw "nixVersions.unstable has been removed. For bleeding edge (Nix master, roughly weekly updated) use nixVersions.git, otherwise use nixVersions.latest.";
236 }
···172 enableParallelChecking = false;
173 };
174000000175 nix_2_24 = common {
176 version = "2.24.10";
177 hash = "sha256-XdeVy1/d6DEIYb3nOA6JIYF4fwMKNxtwJMgT3pHi+ko=";
···224 attr = "nix_2_${toString minor}";
225 in
226 lib.nameValuePair attr (throw "${attr} has been removed")
227+ ) (lib.range 4 23))
228 // {
229 unstable = throw "nixVersions.unstable has been removed. For bleeding edge (Nix master, roughly weekly updated) use nixVersions.git, otherwise use nixVersions.latest.";
230 }
+1
pkgs/top-level/aliases.nix
···780 ### M ###
781782 ma1sd = throw "ma1sd was dropped as it is unmaintained"; # Added 2024-07-10
0783 MACS2 = macs2; # Added 2023-06-12
784 mailctl = throw "mailctl has been renamed to oama"; # Added 2024-08-19
785 mailman-rss = throw "The mailman-rss package was dropped since it was unmaintained."; # Added 2024-06-21
···780 ### M ###
781782 ma1sd = throw "ma1sd was dropped as it is unmaintained"; # Added 2024-07-10
783+ mac = monkeysAudio; # Added 2024-11-30
784 MACS2 = macs2; # Added 2023-06-12
785 mailctl = throw "mailctl has been renamed to oama"; # Added 2024-08-19
786 mailman-rss = throw "The mailman-rss package was dropped since it was unmaintained."; # Added 2024-06-21