Replace the hand-built reference page with Redoc rendering the generated OpenAPI document. The bundle is fetched by package.nix (pinned hash) and served from the UI's own address; only that page gets a style nonce and worker-src blob: in its CSP. The spec now explains authentication, saved-not-applied, ETags (If-Match, 409) and errors, documents the ETag header, gives each operation a curl sample, and no longer names MCP tools. Area GETs no longer emit "parameters": null, which Redoc could not render. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
388 lines
14 KiB
Nix
388 lines
14 KiB
Nix
{
|
|
description = "nixos-router: a NixOS-based distribution for routers/firewalls";
|
|
|
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
|
|
outputs =
|
|
{ self, nixpkgs }:
|
|
let
|
|
systems = [
|
|
"x86_64-linux"
|
|
"aarch64-linux"
|
|
];
|
|
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
|
|
|
|
# What a release is: shown by the web UI, compared by router-update
|
|
# (the serial never goes down: the commit time) and checked against the
|
|
# settings (the schema is the number of migrations/N.jq).
|
|
migrations = builtins.filter (n: nixpkgs.lib.hasSuffix ".jq" n) (
|
|
builtins.attrNames (builtins.readDir ./migrations)
|
|
);
|
|
thisRelease = {
|
|
version = nixpkgs.lib.fileContents ./VERSION;
|
|
serial = self.lastModified or 0;
|
|
rev = self.rev or self.dirtyRev or "unknown";
|
|
schema = builtins.length migrations;
|
|
};
|
|
|
|
devPkgs = nixpkgs.legacyPackages.x86_64-linux;
|
|
# The development helpers (see DEVELOPING.md) as apps.
|
|
devScript =
|
|
name: inputs: file:
|
|
let
|
|
p = devPkgs.writeShellApplication {
|
|
inherit name;
|
|
runtimeInputs = inputs;
|
|
text = builtins.readFile file;
|
|
# Interactive helpers: the word splitting of $@ and the like is intended.
|
|
excludeShellChecks = [ "SC2086" ];
|
|
};
|
|
in
|
|
{
|
|
type = "app";
|
|
program = "${p}/bin/${name}";
|
|
};
|
|
|
|
# Public keys of the update signing key (see `nix run .#release`).
|
|
defaultUpdateKeys = nixpkgs.lib.optional (builtins.pathExists ./keys/update.pub) (
|
|
nixpkgs.lib.fileContents ./keys/update.pub
|
|
);
|
|
in
|
|
{
|
|
nixosModules = {
|
|
router = ./modules/router;
|
|
default = self.nixosModules.router;
|
|
# Disk layout contract shared by router-install and target configs.
|
|
installed-layout = ./modules/installer/layout.nix;
|
|
# The installer pieces; `lib.mkInstaller` wires them up.
|
|
installer = ./modules/installer/install-script.nix;
|
|
installer-iso = ./modules/installer/iso.nix;
|
|
# The generic installer's `router-setup` (see lib.setupModule).
|
|
setup = ./modules/installer/setup.nix;
|
|
# Appliance disk image: dm-verity store, volatile root, state on /var.
|
|
image = ./modules/image;
|
|
# Configuration from a settings file (JSON), rebuilt on the device.
|
|
settings = ./modules/settings;
|
|
};
|
|
|
|
# A router built from settings.json; this is what the device's own
|
|
# configuration flake (see mkConfigDir) evaluates.
|
|
lib.mkRouterSystem =
|
|
{
|
|
settings,
|
|
modules ? [ ],
|
|
updateKeys ? defaultUpdateKeys,
|
|
# The configuration flake passes it on: a `path:` flake has no commit.
|
|
release ? thisRelease,
|
|
}:
|
|
nixpkgs.lib.nixosSystem {
|
|
system = "x86_64-linux";
|
|
modules = [
|
|
self.nixosModules.router
|
|
self.nixosModules.settings
|
|
self.nixosModules.installed-layout
|
|
{
|
|
routerSettings.file = settings;
|
|
routerSettings.release = release;
|
|
routerUpdate.publicKeys = updateKeys;
|
|
# The device rebuilds itself, offline if need be: it needs the
|
|
# sources its configuration flake points to.
|
|
system.extraDependencies = [
|
|
self.outPath
|
|
nixpkgs.outPath
|
|
];
|
|
}
|
|
]
|
|
++ modules;
|
|
};
|
|
|
|
# The configuration directory installed to /var/lib/router/config: a
|
|
# flake pinned to exactly this router source, the settings and an
|
|
# optional local.nix with extra NixOS configuration.
|
|
lib.mkConfigDir =
|
|
{
|
|
pkgs,
|
|
settings,
|
|
local ? null,
|
|
}:
|
|
pkgs.runCommand "router-config" { } ''
|
|
mkdir -p $out
|
|
cp ${self.lib.configFlake pkgs} $out/flake.nix
|
|
cp ${settings} $out/settings.json
|
|
${pkgs.lib.optionalString (local != null) "cp ${local} $out/local.nix"}
|
|
'';
|
|
|
|
# The configuration directory's flake.nix (shared by mkConfigDir and
|
|
# router-setup, so both build exactly the same system).
|
|
lib.configFlake =
|
|
pkgs:
|
|
pkgs.writeText "flake.nix" ''
|
|
{
|
|
inputs.nixos-router.url = "path:${self.outPath}";
|
|
outputs =
|
|
{ nixos-router, ... }:
|
|
{
|
|
nixosConfigurations.router = nixos-router.lib.mkRouterSystem {
|
|
release = {
|
|
version = "${thisRelease.version}";
|
|
serial = ${toString thisRelease.serial};
|
|
rev = "${thisRelease.rev}";
|
|
schema = ${toString thisRelease.schema};
|
|
};
|
|
settings = ./settings.json;
|
|
modules = if builtins.pathExists ./local.nix then [ ./local.nix ] else [ ];
|
|
};
|
|
};
|
|
}
|
|
'';
|
|
|
|
# The generic installer: `router-setup` asks for the basics, builds
|
|
# the router offline and installs it; the rest is done in the web UI.
|
|
# nix build .#installer
|
|
# router-setup's configuration: the flake template and the reference
|
|
# system whose closure makes the offline build possible. `local` is an
|
|
# extra module that the installed systems will import as well.
|
|
lib.setupModule =
|
|
{
|
|
local ? null,
|
|
}:
|
|
{ pkgs, ... }:
|
|
{
|
|
imports = [ self.nixosModules.setup ];
|
|
router.setup = {
|
|
flake = self.lib.configFlake pkgs;
|
|
reference = self.lib.referenceSystem { inherit pkgs local; };
|
|
schemaVersion = thisRelease.schema;
|
|
release = self.lib.mkRelease {
|
|
inherit pkgs;
|
|
includeReference = false;
|
|
};
|
|
};
|
|
};
|
|
|
|
# A router built the way installed ones are: its closure carries what
|
|
# building any other settings needs, so it can be done offline.
|
|
lib.referenceSystem =
|
|
{
|
|
pkgs,
|
|
local ? null,
|
|
}:
|
|
(self.lib.mkRouterSystem {
|
|
settings = builtins.toFile "settings.json" (
|
|
builtins.toJSON {
|
|
version = thisRelease.schema;
|
|
router = {
|
|
wan.interface = "eth0";
|
|
lans.lan = {
|
|
interface = "eth1";
|
|
internet = true;
|
|
ipv4.address = "192.168.1.1";
|
|
dhcp.pool = {
|
|
start = "192.168.1.100";
|
|
end = "192.168.1.200";
|
|
};
|
|
};
|
|
};
|
|
}
|
|
);
|
|
# A path, not a derivation: the module system would read its attributes.
|
|
modules = pkgs.lib.optional (local != null) "${local}";
|
|
}).config.system.build.toplevel;
|
|
|
|
# An update: this source, its nixpkgs, the configuration flake that
|
|
# points at them and (unless the router has them already) the reference
|
|
# system with everything its offline builds need. router-update takes
|
|
# this store path from a signed binary cache (`nix run .#release`).
|
|
lib.mkRelease =
|
|
{
|
|
pkgs,
|
|
includeReference ? true,
|
|
}:
|
|
pkgs.runCommand "router-release-${thisRelease.version}" { } ''
|
|
mkdir -p $out/migrations
|
|
printf %s ${thisRelease.version} > $out/version
|
|
printf %s ${toString thisRelease.serial} > $out/serial
|
|
printf %s ${toString thisRelease.schema} > $out/schema
|
|
printf %s ${thisRelease.rev} > $out/rev
|
|
cp ${self.lib.configFlake pkgs} $out/flake.nix
|
|
for f in ${nixpkgs.lib.concatMapStringsSep " " (n: "${./migrations}/${n}") migrations}; do
|
|
cp "$f" $out/migrations/
|
|
done
|
|
# These references are what make the closure.
|
|
echo ${self.outPath} ${nixpkgs.outPath} \
|
|
${nixpkgs.lib.optionalString includeReference (self.lib.referenceSystem { inherit pkgs; })} \
|
|
> $out/closure
|
|
'';
|
|
|
|
# `nix run .#release -- --key KEY [--out DIR]`: signs a release as a
|
|
# binary cache and writes the bundle and the pull-server layout.
|
|
lib.mkReleaseTool =
|
|
{ pkgs, release }:
|
|
pkgs.writeShellApplication {
|
|
name = "router-release";
|
|
runtimeInputs = with pkgs; [
|
|
nix
|
|
gnutar
|
|
coreutils
|
|
jq
|
|
];
|
|
text = ''
|
|
release=${release}
|
|
${builtins.readFile ./scripts/release.sh}
|
|
'';
|
|
};
|
|
|
|
packages.x86_64-linux.release = self.lib.mkRelease { pkgs = nixpkgs.legacyPackages.x86_64-linux; };
|
|
apps.x86_64-linux.release =
|
|
let
|
|
tool = self.lib.mkReleaseTool {
|
|
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
|
release = self.packages.x86_64-linux.release;
|
|
};
|
|
in
|
|
{
|
|
type = "app";
|
|
program = "${tool}/bin/router-release";
|
|
};
|
|
|
|
# The generic installer ISO:
|
|
# nix build .#installer
|
|
nixosConfigurations.installer = nixpkgs.lib.nixosSystem {
|
|
system = "x86_64-linux";
|
|
modules = [
|
|
self.nixosModules.installer-iso
|
|
(self.lib.setupModule { })
|
|
];
|
|
};
|
|
packages.x86_64-linux.installer = self.nixosConfigurations.installer.config.system.build.isoImage;
|
|
|
|
# Installer ISO for a given nixosSystem:
|
|
# nix build .#installer-example (see packages)
|
|
lib.mkInstaller =
|
|
{ configuration }:
|
|
nixpkgs.lib.nixosSystem {
|
|
inherit (configuration.pkgs.stdenv.hostPlatform) system;
|
|
modules = [
|
|
self.nixosModules.installer-iso
|
|
{
|
|
router.installer.system = configuration.config.system.build.toplevel;
|
|
# Reach the live system with the keys that will reach the router.
|
|
users.users.root.openssh.authorizedKeys.keys =
|
|
configuration.config.users.users.root.openssh.authorizedKeys.keys;
|
|
}
|
|
];
|
|
};
|
|
|
|
packages.x86_64-linux.installer-example =
|
|
(self.lib.mkInstaller { configuration = self.nixosConfigurations.example; })
|
|
.config.system.build.isoImage;
|
|
|
|
# A deployable starting point; copy examples/desktop and adapt it.
|
|
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
|
|
system = "x86_64-linux";
|
|
modules = [
|
|
self.nixosModules.router
|
|
./examples/desktop
|
|
];
|
|
};
|
|
|
|
# Appliance image (verity store, volatile root) for trying on hardware:
|
|
# nix build .#image -> result/*.raw
|
|
nixosConfigurations.image = nixpkgs.lib.nixosSystem {
|
|
system = "x86_64-linux";
|
|
modules = [
|
|
self.nixosModules.router
|
|
self.nixosModules.image
|
|
./examples/image
|
|
];
|
|
};
|
|
|
|
packages.x86_64-linux.image = self.nixosConfigurations.image.config.system.build.image;
|
|
|
|
# `nix run .#deploy -- <flake-attr> <user@host>`: activate a new
|
|
# configuration with commit-confirm; see scripts/deploy.sh.
|
|
apps.x86_64-linux.deploy =
|
|
let
|
|
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
|
deploy = pkgs.writeShellApplication {
|
|
name = "router-deploy";
|
|
runtimeInputs = with pkgs; [
|
|
nixos-rebuild
|
|
openssh
|
|
coreutils
|
|
];
|
|
text = builtins.readFile ./scripts/deploy.sh;
|
|
};
|
|
in
|
|
{
|
|
type = "app";
|
|
program = "${deploy}/bin/router-deploy";
|
|
};
|
|
|
|
# Development loop (see DEVELOPING.md):
|
|
# nix run .#fast quick checks, no VMs
|
|
# nix run .#test -- update VM tests, one at a time, with a summary
|
|
# nix run .#webui-dev the web UI locally, templates hot-reload
|
|
apps.x86_64-linux.fast = devScript "router-fast" (with devPkgs; [
|
|
git
|
|
go_1_27
|
|
nix
|
|
coreutils
|
|
gnugrep
|
|
]) ./scripts/fast.sh;
|
|
apps.x86_64-linux.test = devScript "router-test" (with devPkgs; [
|
|
nix
|
|
coreutils
|
|
gnugrep
|
|
gnused
|
|
]) ./scripts/test.sh;
|
|
apps.x86_64-linux.webui-dev = devScript "router-webui-dev" (with devPkgs; [
|
|
git
|
|
go_1_27
|
|
iproute2
|
|
jq
|
|
curl
|
|
coreutils
|
|
]) ./scripts/webui-dev.sh;
|
|
|
|
# VM tests need KVM; only wired up for x86_64 until aarch64 CI exists.
|
|
checks.x86_64-linux =
|
|
import ./tests {
|
|
inherit self;
|
|
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
|
}
|
|
// {
|
|
# The example must keep building as a complete system.
|
|
example = self.nixosConfigurations.example.config.system.build.toplevel;
|
|
};
|
|
|
|
# `nix run .#lab`: boot the basic test topology in tmux, one console pane
|
|
# per VM plus the interactive test-driver REPL.
|
|
apps.x86_64-linux.lab =
|
|
let
|
|
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
|
test = self.checks.x86_64-linux.basic;
|
|
lab = pkgs.writeShellApplication {
|
|
name = "router-lab";
|
|
runtimeInputs = with pkgs; [
|
|
tmux
|
|
util-linux
|
|
gnugrep
|
|
coreutils
|
|
];
|
|
text = import ./tests/lab.nix {
|
|
inherit (pkgs) lib;
|
|
driver = test.driverInteractive;
|
|
nodes = builtins.attrNames test.nodes;
|
|
};
|
|
};
|
|
in
|
|
{
|
|
type = "app";
|
|
program = "${lab}/bin/router-lab";
|
|
};
|
|
|
|
formatter = forAllSystems (pkgs: pkgs.nixfmt);
|
|
};
|
|
}
|