This commit is contained in:
Jeeves 2024-07-29 22:48:13 -06:00
commit 7699cea553
5 changed files with 426 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
result
config.nix

27
flake.lock generated Normal file
View file

@ -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
}

89
flake.nix Normal file
View file

@ -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}";
# };
};
};
};
}

14
openwrt-rebuild.sh Normal file
View file

@ -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

294
utils.nix Normal file
View file

@ -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"))
])
];
};
}