pkgs: let mkSection = name: config: options: if builtins.hasAttr name config then let attr = builtins.getAttr name config; in if attr != {} then pkgs.lib.concatStrings ( [ "config ${name}\n" ] ++ (map (o: o attr) options) ++ [ "\n\n" ] ) else "" else ""; mkSections = name: config: options: if builtins.hasAttr name config then pkgs.lib.concatStrings ( pkgs.lib.attrsets.mapAttrsToList (k: v: pkgs.lib.concatStrings ( [ "config ${name} '${k}'\n" ] ++ (map (o: o v) options) ++ [ "\n\n" ] ) ) (builtins.getAttr name config) ) else ""; mkSectionsList = name: config: options: if builtins.hasAttr name config then pkgs.lib.concatStrings ( map (v: pkgs.lib.concatStrings ( [ "config ${name}\n" ] ++ (map (o: o v) options) ++ [ "\n\n" ] ) ) (builtins.getAttr name config) ) else ""; option = name: value: "\toption ${name} '${toString value}'\n"; list = name: value: "\tlist ${name} '${toString value}'\n"; mkOptional = name: { config, typeFn }: if builtins.hasAttr name config then typeFn { inherit name config; } else ""; mkMandatory = name: { config, typeFn }: assert pkgs.lib.asserts.assertMsg (builtins.hasAttr name config) "missing required attribute '${name}'"; typeFn { inherit name config; }; typeList = optionFn: config: optionFn { inherit config; typeFn = { name, config }: let attr = builtins.getAttr name config; type = builtins.typeOf attr; in assert pkgs.lib.asserts.assertMsg (type == "list") "attribute '${name}' expected list, found ${builtins.typeOf attr}"; pkgs.lib.concatStrings (map (v: list name v) attr); }; typeString = optionFn: config: optionFn { inherit config; typeFn = { name, config }: let attr = builtins.getAttr name config; type = builtins.typeOf attr; in assert pkgs.lib.asserts.assertMsg (type == "string") "attribute '${name}' expected string, found ${builtins.typeOf attr}"; option name attr; }; typeBool = optionFn: config: optionFn { inherit config; typeFn = { name, config }: let attr = builtins.getAttr name config; type = builtins.typeOf attr; in assert pkgs.lib.asserts.assertMsg (type == "bool") "attribute '${name}' expected bool, found ${builtins.typeOf attr}"; option name (if attr then "1" else "0"); }; typeFirewall = optionFn: config: optionFn { inherit config; typeFn = { name, config }: let attr = builtins.getAttr name config; type = builtins.typeOf attr; in assert pkgs.lib.asserts.assertMsg (type == "string") "attribute '${name}' expected string, found ${builtins.typeOf attr}"; assert pkgs.lib.asserts.assertMsg ( attr == "ACCEPT" || attr == "REJECT" || attr == "DROP" || attr == "NOTRACK" || attr == "HELPER" || attr == "MARK" || attr == "DSCP" ) "attribute '${name}' must be one of ACCEPT, REJECT, DROP, NOTRACK, HELPER, MARK, DSCP"; option name attr; }; typeInt = optionFn: config: optionFn { inherit config; typeFn = { name, config }: let attr = builtins.getAttr name config; type = builtins.typeOf attr; in assert pkgs.lib.asserts.assertMsg (type == "int") "attribute '${name}' expected int, found ${builtins.typeOf attr}"; option name attr; }; in { buildConfig = path: let config = import path; in { dhcp = pkgs.lib.concatStrings [ (mkSection "dnsmasq" config.dhcp [ (typeBool (mkOptional "domainneeded")) (typeBool (mkOptional "boguspriv")) (typeBool (mkOptional "filterwin2k")) (typeBool (mkOptional "localise_queries")) (typeBool (mkOptional "rebind_protection")) (typeBool (mkOptional "rebind_localhost")) (typeString (mkOptional "local")) (typeString (mkOptional "domain")) (typeBool (mkOptional "expandhosts")) (typeBool (mkOptional "nonegcache")) (typeInt (mkOptional "cachesize")) (typeBool (mkOptional "authoritative")) (typeBool (mkOptional "readethers")) (typeString (mkOptional "leasefile")) (typeString (mkOptional "resolvfile")) (typeBool (mkOptional "nonwildcard")) (typeBool (mkOptional "localservice")) (typeInt (mkOptional "ednspacket_max")) (typeBool (mkOptional "filter_a")) (typeBool (mkOptional "filter_aaaa")) ]) (mkSections "dhcp" config.dhcp [ (typeString (mkOptional "interface")) (typeBool (mkOptional "ignore")) (typeInt (mkOptional "start")) (typeInt (mkOptional "limit")) (typeString (mkOptional "leasetime")) (typeString (mkOptional "dhcpv4")) (typeString (mkOptional "dhcpv6")) (typeString (mkOptional "ra")) (typeBool (mkOptional "ra_slaac")) ]) (mkSections "odhcpd" config.dhcp [ (typeBool (mkOptional "maindhcp")) (typeString (mkOptional "leasefile")) (typeString (mkOptional "leasetrigger")) (typeInt (mkOptional "loglevel")) ]) ]; dropbear = pkgs.lib.concatStrings [ (mkSections "dropbear" config.dropbear [ (typeString (mkOptional "PasswordAuth")) (typeString (mkOptional "RootPasswordAuth")) (typeInt (mkOptional "Port")) (typeString (mkOptional "BannerFile")) ]) ]; firewall = pkgs.lib.concatStrings [ (mkSection "defaults" config.firewall [ (typeBool (mkOptional "syn_flood")) (typeFirewall (mkOptional "input")) (typeFirewall (mkOptional "output")) (typeFirewall (mkOptional "forward")) (typeBool (mkOptional "disable_ipv6")) ]) (mkSectionsList "zone" config.firewall [ (typeString (mkMandatory "name")) (typeList (mkMandatory "network")) (typeFirewall (mkOptional "input")) (typeFirewall (mkOptional "output")) (typeFirewall (mkOptional "forward")) (typeBool (mkOptional "masq")) (typeBool (mkOptional "mtu_fix")) ]) (mkSectionsList "forwarding" config.firewall [ (typeString (mkMandatory "src")) (typeString (mkMandatory "dest")) ]) (mkSectionsList "rule" config.firewall [ (typeString (mkMandatory "name")) (typeString (mkOptional "src")) (typeInt (mkOptional "src_port")) (typeString (mkMandatory "proto")) (typeString (mkOptional "dest")) (typeInt (mkOptional "dest_port")) (typeFirewall (mkMandatory "target")) (typeString (mkOptional "family")) (typeList (mkOptional "icmp_type")) ]) (mkSectionsList "redirect" config.firewall [ (typeString (mkMandatory "name")) (typeString (mkOptional "src")) (typeString (mkOptional "src_ip")) (typeString (mkOptional "src_mac")) (typeInt (mkOptional "src_port")) (typeInt (mkOptional "src_dport")) (typeString (mkOptional "dest_ip")) (typeInt (mkOptional "dest_port")) (typeString (mkOptional "proto")) ]) ]; network = pkgs.lib.concatStrings [ (mkSections "interface" config.network [ (typeString (mkMandatory "device")) (typeString (mkMandatory "proto")) (typeString (mkOptional "ipaddr")) (typeString (mkOptional "netmask")) (typeInt (mkOptional "ip6assign")) ]) (mkSections "globals" config.network [ (typeString (mkOptional "ula_prefix")) ]) (mkSectionsList "device" config.network [ (typeString (mkMandatory "name")) (typeString (mkOptional "type")) (typeList (mkOptional "ports")) ]) ]; system = pkgs.lib.concatStrings [ (mkSection "system" config.system [ (typeString (mkOptional "hostname")) (typeString (mkOptional "timezone")) (typeBool (mkOptional "ttylogin")) (typeInt (mkOptional "log_size")) (typeInt (mkOptional "urandom_seed")) (typeString (mkOptional "compat_version")) (typeString (mkOptional "zonename")) (typeString (mkOptional "log_proto")) (typeInt (mkOptional "conloglevel")) (typeInt (mkOptional "cronloglevel")) ]) (mkSections "timeserver" config.system [ (typeList (mkOptional "server")) ]) (mkSections "led" config.system [ (typeString (mkOptional "name")) (typeString (mkOptional "sysfs")) (typeBool (mkOptional "default")) ]) ]; wireless = pkgs.lib.concatStrings [ (mkSections "wifi-device" config.wireless [ (typeString (mkOptional "type")) (typeString (mkOptional "path")) (typeInt (mkOptional "channel")) (typeString (mkOptional "band")) (typeString (mkOptional "htmode")) (typeBool (mkOptional "disabled")) ]) (mkSections "wifi-iface" config.wireless [ (typeString (mkOptional "device")) (typeString (mkOptional "network")) (typeString (mkOptional "mode")) (typeString (mkOptional "ssid")) (typeString (mkOptional "encryption")) ]) ]; uhttpd = pkgs.lib.concatStrings [ (mkSections "uhttpd" config.uhttpd [ (typeBool (mkOptional "redirect_https")) (typeString (mkOptional "home")) (typeBool (mkOptional "rfc1918_filter")) (typeInt (mkOptional "max_requests")) (typeInt (mkOptional "max_connections")) (typeString (mkOptional "cert")) (typeString (mkOptional "key")) (typeString (mkOptional "cgi_prefix")) (typeInt (mkOptional "script_timeout")) (typeInt (mkOptional "network_timeout")) (typeInt (mkOptional "http_keepalive")) (typeBool (mkOptional "tcp_keepalive")) (typeString (mkOptional "ubus_prefix")) ]) (mkSections "cert" config.uhttpd [ (typeInt (mkOptional "days")) (typeString (mkOptional "key_type")) (typeInt (mkOptional "bits")) (typeString (mkOptional "ec_curve")) (typeString (mkOptional "country")) (typeString (mkOptional "state")) (typeString (mkOptional "location")) (typeString (mkOptional "commonname")) ]) ]; }; }