Add signed updates from a bundle or a URL
router-release signs a release as a binary cache; router-update verifies it against the router's update keys, migrates settings.json, and applies it with commit-confirm. Includes the web UI page and a VM test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
@@ -11,6 +11,42 @@
|
||||
"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 = {
|
||||
@@ -35,6 +71,9 @@
|
||||
{
|
||||
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";
|
||||
@@ -44,6 +83,8 @@
|
||||
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 = [
|
||||
@@ -82,6 +123,12 @@
|
||||
{ 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 [ ];
|
||||
};
|
||||
@@ -104,30 +151,99 @@
|
||||
imports = [ self.nixosModules.setup ];
|
||||
router.setup = {
|
||||
flake = self.lib.configFlake pkgs;
|
||||
reference =
|
||||
(self.lib.mkRouterSystem {
|
||||
settings = builtins.toFile "settings.json" (
|
||||
builtins.toJSON {
|
||||
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;
|
||||
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 {
|
||||
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 {
|
||||
@@ -215,6 +331,31 @@
|
||||
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
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user