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")) (typeString (mkOptional "gateway")) (typeString (mkOptional "broadcast")) (typeString (mkOptional "ip6addr")) (typeString (mkOptional "ip6gw")) (typeInt (mkOptional "ip6assign")) (typeList (mkOptional "dns")) (typeInt (mkOptional "layer")) ]) (mkSections "globals" config.network [ (typeString (mkOptional "ula_prefix")) (typeBool (mkOptional "packet_steering")) ]) (mkSectionsList "device" config.network [ (typeString (mkMandatory "name")) (typeString (mkOptional "macaddr")) (typeString (mkOptional "type")) (typeString (mkOptional "ifname")) (typeList (mkOptional "ports")) ]) ]; system = pkgs.lib.concatStrings [ (mkSection "system" config.system [ (typeString (mkOptional "hostname")) (typeString (mkOptional "description")) (typeString (mkOptional "notes")) (typeInt (mkOptional "buffersize")) (typeInt (mkOptional "conloglevel")) (typeInt (mkOptional "cronloglevel")) (typeInt (mkOptional "klogconloglevel")) (typeInt (mkOptional "log_buffer_size")) (typeString (mkOptional "log_file")) (typeString (mkOptional "log_hostname")) (typeString (mkOptional "log_ip")) (typeInt (mkOptional "log_port")) (typeString (mkOptional "log_prefix")) (typeString (mkOptional "log_proto")) (typeInt (mkOptional "log_size")) (typeBool (mkOptional "log_trailer_null")) (typeString (mkOptional "log_type")) (typeBool (mkOptional "ttylogin")) (typeInt (mkOptional "urandom_seed")) (typeString (mkOptional "timezone")) (typeString (mkOptional "zonename")) (typeString (mkOptional "zram_comp_algo")) (typeInt (mkOptional "zram_size_mb")) (typeString (mkOptional "compat_version")) ]) (mkSections "timeserver" config.system [ (typeBool (mkOptional "enabled")) (typeBool (mkOptional "enable_server")) (typeList (mkMandatory "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 "phy")) (typeString (mkOptional "macaddr")) (typeString (mkOptional "path")) (typeBool (mkOptional "disabled")) (typeInt (mkOptional "channel")) # (typeList (mkOptional "channels")) (typeString (mkOptional "hwmode")) (typeString (mkOptional "band")) (typeString (mkOptional "htmode")) (typeInt (mkOptional "chanbw")) (typeString (mkOptional "ht_capab")) (typeInt (mkOptional "txpower")) (typeBool (mkOptional "diversity")) (typeInt (mkOptional "rxantenna")) (typeInt (mkOptional "txantenna")) (typeString (mkOptional "country")) (typeBool (mkOptional "country_ie")) (typeString (mkOptional "distance")) (typeInt (mkOptional "beacon_int")) (typeBool (mkOptional "legacy_rates")) (typeString (mkOptional "require_mode")) (typeInt (mkOptional "cell_density")) # (typeList (mkOptional "basic_rate")) # (typeList (mkOptional "supported_rates")) (typeInt (mkOptional "log_level")) (typeList (mkOptional "hostapd_options")) ]) (mkSections "wifi-iface" config.wireless [ (typeString (mkOptional "ifname")) (typeString (mkOptional "device")) (typeString (mkOptional "network")) (typeString (mkOptional "mode")) (typeBool (mkOptional "disabled")) (typeString (mkOptional "ssid")) (typeString (mkOptional "bssid")) (typeString (mkOptional "mesh_id")) (typeBool (mkOptional "hidden")) (typeBool (mkOptional "isolate")) (typeBool (mkOptional "doth")) (typeBool (mkOptional "wmm")) (typeString (mkOptional "encryption")) (typeString (mkOptional "key")) (typeString (mkOptional "key1")) (typeString (mkOptional "key2")) (typeString (mkOptional "key3")) (typeString (mkOptional "key4")) (typeString (mkOptional "macfilter")) (typeString (mkOptional "iapp_interface")) (typeBool (mkOptional "rsn_preauth")) (typeInt (mkOptional "ieee80211w")) (typeInt (mkOptional "ieee80211w_max_timeout")) (typeInt (mkOptional "ieee80211w_retry_timeout")) (typeBool (mkOptional "sae_require_mfp")) (typeInt (mkOptional "maxassoc")) (typeString (mkOptional "macaddr")) (typeInt (mkOptional "dtim_period")) (typeBool (mkOptional "short_preamble")) (typeInt (mkOptional "max_listen_int")) (typeInt (mkOptional "mcast_rate")) (typeBool (mkOptional "wds")) (typeString (mkOptional "owe_transition_ssid")) (typeString (mkOptional "owe_transition_bssid")) (typeInt (mkOptional "sae_pwe")) (typeInt (mkOptional "ocv")) (typeBool (mkOptional "start_disabled")) (typeBool (mkOptional "default_disabled")) (typeList (mkOptional "hostapd_bss_options")) ]) ]; 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")) ]) ]; }; }