From 7699cea5538f96de5d4ef30827421af43ad0e3c2 Mon Sep 17 00:00:00 2001 From: Jeeves Date: Mon, 29 Jul 2024 22:48:13 -0600 Subject: [PATCH] init --- .gitignore | 2 + flake.lock | 27 +++++ flake.nix | 89 ++++++++++++++ openwrt-rebuild.sh | 14 +++ utils.nix | 294 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 426 insertions(+) create mode 100644 .gitignore create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 openwrt-rebuild.sh create mode 100644 utils.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79ffe03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +result +config.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e46f50e --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1722062969, + "narHash": "sha256-QOS0ykELUmPbrrUGmegAUlpmUFznDQeR4q7rFhl8eQg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "b73c2221a46c13557b1b3be9c2070cc42cf01eb3", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..73cf5f4 --- /dev/null +++ b/flake.nix @@ -0,0 +1,89 @@ +{ + description = "Configure and manage OpenWrt"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + }; + + outputs = { self, nixpkgs }: + let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + in rec { + utils = (import ./utils.nix) pkgs; + + config = utils.buildConfig ./config.nix; + + out = pkgs.stdenvNoCC.mkDerivation { + name = "openwrt"; + dontUnpack = true; + dhcp = pkgs.writeText "dhcp" config.dhcp; + # dropbear = pkgs.writeText "dropbear" config.dropbear; + firewall = pkgs.writeText "firewall" config.firewall; + network = pkgs.writeText "network" config.network; + sys = pkgs.writeText "system" config.system; + wireless = pkgs.writeText "wireless" config.wireless; + installPhase = '' + mkdir -p $out/etc/config + cd $out/etc/config + cp $dhcp dhcp + # cp $dropbear dropbear + cp $firewall firewall + cp $network network + cp $sys system + cp $wireless wireless + ''; + }; + + # openwrtInstall = pkgs.stdenvNoCC.mkDerivation { + # name = "openwrtInstall"; + # dontUnpack = true; + # installPhase = '' + + # ''; + # }; + openwrt-rebuild = pkgs.stdenvNoCC.mkDerivation ( + let rebuild = ./openwrt-rebuild.sh; in { + name = "openwrt-rebuild"; + dontUnpack = true; + installPhase = "cp $src $out"; + src = pkgs.writeShellScript "openwrt-rebuild" '' + ssh root@spiffyap.lan 'bash -s' < ${rebuild} + scp -r ${out}/* root@spiffyap.lan:/etc/nix-current + ''; + buildInputs = with pkgs; [ openssh ]; + }); + packages.x86_64-linux.default = openwrt-rebuild; + + nixosModules.default = { config, ... }: + with pkgs.lib; + let cfg = config.openwrt; in { + options = { + enable = mkEnableOption "Enable OpenWrt management module"; + }; + config = mkIf cfg.enable { + system.activationScripts.openwrtInstall = { + text = '' + ssh root@spiffyap.lan 'bash -s' < ${openwrt-rebuild} + ''; + deps = [ "specialfs" ]; + }; + # system.activationScripts.openwrtNewGeneration = { + # text = '' + # generation="$(basename "$(readlink /var/openwrt)" || echo 0)" + # (( ++generation )) + # echo "[openwrt] creating new generation in /var/openwrt/$generation" + # mkdir -p "/var/openwrt_mnt" + # chmod 0751 "/var/openwrt_mnt" + # ${mountCommand} + # mkdir -p "" + # ''; + # deps = [ "specialfs" ]; + # }; + # systemd.services.openwrt = { + # wantedBy = [ ]; + # serviceConfig.ExecStart = "${packages.default}"; + # }; + }; + }; + }; +} diff --git a/openwrt-rebuild.sh b/openwrt-rebuild.sh new file mode 100644 index 0000000..0fd5f27 --- /dev/null +++ b/openwrt-rebuild.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +mkdir -p /etc/nix +generation="$(basename "$(readlink /etc/nix-current)" || echo 0)" +((generation++)) +echo "[nix-openwrt] creating new generation in /etc/nix/$generation" +mkdir -p "/etc/nix/$generation" +rm "/etc/nix-current" +ln -s "/etc/nix/$generation" "/etc/nix-current" + +ln -s "/etc/nix-current/etc/config/wireless" "/etc/config/wireless" +reload_config +# mkdir -p "/etc/nix-current/config" +# rm /etc/config/wireless +# ln -s /etc/nix-current/config/wireless /etc/config/wireless diff --git a/utils.nix b/utils.nix new file mode 100644 index 0000000..470def4 --- /dev/null +++ b/utils.nix @@ -0,0 +1,294 @@ +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")) + ]) + ]; + }; +}