Compare commits

..

1 Commits

Author SHA1 Message Date
84c6a7af6a feat: bidirectional remote builders — server <> uConsole
Add reusable remote-builder NixOS module and builder system user.

Server (lazyworkhorse): dispatches aarch64-linux builds to uConsole
uConsole: dispatches x86_64-linux builds to server

The builder user uses the same SSH keypair on both hosts for
symmetric remote building. Generate the key with:
  ssh-keygen -t ed25519 -f /etc/ssh/builder_key -N ""
Add the public key to lib/keys.nix (replace PLACEHOLDER).
2026-06-20 20:59:38 -04:00
7 changed files with 203 additions and 159 deletions

186
flake.nix
View File

@@ -12,21 +12,18 @@
url = "git+https://git.lix.systems/lix-project/lix?ref=main"; url = "git+https://git.lix.systems/lix-project/lix?ref=main";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nixpkgs-uconsole.url = "github:NixOS/nixpkgs/nixos-25.11";
nixos-uconsole = { nixos-uconsole = {
url = "github:nixos-uconsole/nixos-uconsole/v1.1.0"; url = "github:nixos-uconsole/nixos-uconsole";
inputs.nixpkgs.follows = "nixpkgs-uconsole"; inputs.nixpkgs.follows = "nixpkgs";
inputs.nixos-raspberrypi.follows = "nixos-raspberrypi";
}; };
nixos-raspberrypi = { nixos-raspberrypi = {
url = "github:gortium/nixos-raspberrypi/cm5-cross-v1"; url = "github:nvmd/nixos-raspberrypi/v1.20260517.0";
inputs.nixpkgs.follows = "nixpkgs-uconsole"; inputs.nixpkgs.follows = "nixpkgs";
}; };
self.submodules = true;
}; };
outputs = { self, nixpkgs, agenix, lix outputs = { self, nixpkgs, agenix, lix, nixos-uconsole, nixos-raspberrypi, ... }@inputs:
, nixpkgs-uconsole, nixos-uconsole, nixos-raspberrypi
, ... }@inputs:
let let
system = "x86_64-linux"; system = "x86_64-linux";
keys = import ./lib/keys.nix; keys = import ./lib/keys.nix;
@@ -37,120 +34,81 @@
"/etc/ssh/ssh_host_ed25519_key" "/etc/ssh/ssh_host_ed25519_key"
"/root/.age/bootstrap.key" ]; "/root/.age/bootstrap.key" ];
}; };
overlays = [ agenix.overlays.default ]; overlays = [ agenix.overlays.default (import ./overlays/reticulum.nix) ];
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system overlays; inherit system overlays;
config.allowUnfree = true; config.allowUnfree = true;
config.permittedInsecurePackages = [ "openclaw-2026.3.12" ]; config.permittedInsecurePackages = [
"openclaw-2026.3.12"
];
}; };
devShell = import ./shells/nix_dev.nix { devShell = import ./shells/nix_dev.nix {
inherit pkgs system agenix; inherit pkgs system agenix;
}; };
in { in
nixosConfigurations = { {
lazyworkhorse = nixpkgs.lib.nixosSystem { nixosConfigurations = {
specialArgs = { inherit system self keys paths inputs; }; lazyworkhorse = nixpkgs.lib.nixosSystem {
modules = [ specialArgs = { inherit system self keys paths inputs; };
{ modules = [
nixpkgs.overlays = overlays; {
nixpkgs.config.allowUnfree = true; nixpkgs.overlays = overlays;
nixpkgs.config.rocmSupport = true; nixpkgs.config.allowUnfree = true;
nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" ]; nixpkgs.config.rocmSupport = true;
nix.package = lix.packages.${system}.default; nixpkgs.config.permittedInsecurePackages = [
} "openclaw-2026.3.12"
agenix.nixosModules.default ];
./hosts/lazyworkhorse/configuration.nix nix.package = lix.packages.${system}.default;
./hosts/lazyworkhorse/hardware-configuration.nix }
./modules/nixos/filesystem/hoardingcow-mount.nix agenix.nixosModules.default
./modules/nixos/services/docker_manager.nix ./hosts/lazyworkhorse/configuration.nix
./modules/nixos/services/open_code_server.nix ./hosts/lazyworkhorse/hardware-configuration.nix
./modules/nixos/services/ollama_init_custom_models.nix ./modules/nixos/filesystem/hoardingcow-mount.nix
./modules/nixos/services/openclaw_node.nix ./modules/nixos/services/docker_manager.nix
./modules/nixos/security/ai-worker-restricted.nix ./modules/nixos/services/open_code_server.nix
./users/gortium.nix ./modules/nixos/services/ollama_init_custom_models.nix
./users/ai-worker.nix ./modules/nixos/services/openclaw_node.nix
]; ./modules/nixos/services/remote-builder.nix
}; ./modules/nixos/security/ai-worker-restricted.nix
./users/gortium.nix
cyt-pi = nixpkgs.lib.nixosSystem { ./users/ai-worker.nix
specialArgs = { inherit self keys paths inputs; }; ./users/builder.nix
modules = [ ];
{
nixpkgs.overlays = overlays;
nixpkgs.config.allowUnfree = true;
nixpkgs.hostPlatform = "aarch64-linux";
nix.package = lix.packages."aarch64-linux".default;
}
./hosts/cyt-pi/configuration.nix
./hosts/cyt-pi/hardware-configuration.nix
];
};
uconsole-cm5 = nixpkgs-uconsole.lib.nixosSystem {
system = "aarch64-linux";
specialArgs = {
inherit self keys paths inputs;
nixos-raspberrypi = nixos-raspberrypi;
isCM4 = false;
}; };
modules = [
{
nixpkgs.buildPlatform = "x86_64-linux";
nixpkgs.hostPlatform = "aarch64-linux";
nixpkgs.config.allowUnfree = true;
boot.loader.raspberry-pi.bootloader = "kernel";
}
nixos-raspberrypi.nixosModules.nixpkgs-rpi
# Fix display timings from Rex's kernel: native 720x1280 instead of
# rotated 1280x720, plus DCS-based panel detection.
({ lib, ... }: {
boot.kernelPatches = [{
name = "panel-cwu50-rex-timing-fix";
patch = ./patches/0008-panel-cwu50-rex-timing-fix.patch;
}];
})
nixos-raspberrypi.nixosModules.raspberry-pi-5.base
nixos-raspberrypi.lib.inject-overlays
nixos-raspberrypi.lib.inject-overlays-global
nixos-uconsole.nixosModules.uconsole-cm5
({ config, lib, pkgs, inputs, ... }: let
lix-cross = import inputs.nixpkgs-uconsole {
localSystem = { system = "x86_64-linux"; };
crossSystem = { system = "aarch64-linux"; };
overlays = [ inputs.lix.overlays.default ];
};
in { nix.package = lix-cross.lix; })
agenix.nixosModules.default
./hosts/uconsole-cm5/configuration.nix
./hosts/uconsole-cm5/hardware-configuration.nix
];
};
};
devShells.${system}.default = devShell; cyt-pi = nixpkgs.lib.nixosSystem {
specialArgs = { inherit self keys paths inputs; };
packages.${system} = { modules = [
uconsole-cm5-image = (nixos-raspberrypi.lib.nixosSystem { {
system = "aarch64-linux"; nixpkgs.overlays = overlays;
specialArgs = { nixpkgs.config.allowUnfree = true;
inherit self keys inputs; nixpkgs.hostPlatform = "aarch64-linux";
nixos-raspberrypi = nixos-raspberrypi; nix.package = lix.packages."aarch64-linux".default;
isCM4 = false; }
./hosts/cyt-pi/configuration.nix
./hosts/cyt-pi/hardware-configuration.nix
];
}; };
modules = [
{ uConsole = nixos-raspberrypi.lib.nixosSystem {
nixpkgs.buildPlatform = system; specialArgs = { inherit self keys paths inputs nixos-raspberrypi; };
nixpkgs.hostPlatform = "aarch64-linux"; modules = [
} {
nixos-raspberrypi.nixosModules.nixpkgs-rpi nixpkgs.overlays = overlays;
nixos-raspberrypi.nixosModules.raspberry-pi-5.base nixpkgs.config.allowUnfree = true;
nixos-raspberrypi.lib.inject-overlays-global nixpkgs.hostPlatform = "aarch64-linux";
nixos-raspberrypi.nixosModules.sd-image nix.package = lix.packages."aarch64-linux".default;
nixos-uconsole.nixosModules.uconsole-cm5 }
agenix.nixosModules.default nixos-raspberrypi.nixosModules.raspberry-pi-5.base
./hosts/uconsole-cm5/configuration.nix nixos-uconsole.nixosModules.uconsole-cm5
]; ./modules/nixos/services/remote-builder.nix
}).config.system.build.sdImage; ./hosts/uConsole/configuration.nix
./users/builder.nix
./hosts/uConsole/hardware-configuration.nix
];
};
};
devShells.${system}.default = devShell;
}; };
};
} }

View File

@@ -573,5 +573,23 @@
# For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
system.stateVersion = "25.05"; # Did you read the comment? system.stateVersion = "25.05"; # Did you read the comment?
# ============================================================
# Remote builder — dispatches aarch64-linux builds to uConsole
# ============================================================
services.remoteBuilder = {
enable = true;
machines = [
{
hostName = "192.168.1.120";
port = 22;
sshUser = "builder";
sshKey = "/etc/ssh/builder_key";
systems = [ "aarch64-linux" ];
maxJobs = 4;
}
];
};
} }

View File

@@ -168,4 +168,23 @@
# Reticulum uses its own encryption and doesn't need open ports # Reticulum uses its own encryption and doesn't need open ports
# for basic mesh operations (peer-to-peer discovery). # for basic mesh operations (peer-to-peer discovery).
# For TCP interfaces, open additional ports as needed. # For TCP interfaces, open additional ports as needed.
# ============================================================
# Remote builder — dispatches x86_64-linux builds to server
# ============================================================
services.remoteBuilder = {
enable = true;
machines = [
{
hostName = "lazyworkhorse.net";
port = 2424;
sshUser = "builder";
sshKey = "/etc/ssh/builder_key";
systems = [ "x86_64-linux" ];
maxJobs = 36;
supportedFeatures = [ "benchmark" "big-parallel" "nixos-test" ];
}
];
};
} }

View File

@@ -9,6 +9,13 @@
ai-worker = { ai-worker = {
main = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXeGtPPcsP2IYRQNvII41NVWhJsarEk8c4qxs/a5sXf"; main = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAXeGtPPcsP2IYRQNvII41NVWhJsarEk8c4qxs/a5sXf";
}; };
builder = {
# Same key on both hosts for bidirectional remote building.
# Generate with: ssh-keygen -t ed25519 -f /etc/ssh/builder_key -N ""
# Replace the placeholder below with the public key (builder_key.pub).
main = "PLACEHOLDER_ADD_BUILDER_PUBKEY_HERE";
};
}; };
hosts = { hosts = {

View File

@@ -0,0 +1,74 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.remoteBuilder;
in {
options.services.remoteBuilder = {
enable = lib.mkEnableOption "remote Nix build machine";
machines = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
hostName = lib.mkOption {
type = lib.types.str;
description = "Hostname or IP of the remote build machine.";
};
port = lib.mkOption {
type = lib.types.port;
default = 22;
description = "SSH port.";
};
sshUser = lib.mkOption {
type = lib.types.str;
default = "builder";
description = "SSH user on the remote build machine.";
};
sshKey = lib.mkOption {
type = lib.types.str;
description = "Path to SSH private key for the builder.";
};
systems = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "aarch64-linux" ];
description = "System types the remote builder can build for.";
};
maxJobs = lib.mkOption {
type = lib.types.int;
default = 4;
description = "Max parallel jobs on the remote builder.";
};
supportedFeatures = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ "benchmark" "big-parallel" "nixos-test" ];
description = "Features the remote builder supports.";
};
};
});
default = [];
description = "List of remote Nix build machines.";
};
};
config = lib.mkIf cfg.enable {
nix.distributedBuilds = true;
nix.buildMachines = map (m: {
hostName = m.hostName;
sshUser = m.sshUser;
sshKey = m.sshKey;
systems = m.systems;
maxJobs = m.maxJobs;
supportedFeatures = m.supportedFeatures;
}) cfg.machines;
# SSH config for port + key (nix.buildMachines has no port option)
programs.ssh.extraConfig = lib.concatStringsSep "\n" (map (m: ''
Host ${m.hostName}
HostName ${m.hostName}
Port ${toString m.port}
User ${m.sshUser}
IdentityFile ${m.sshKey}
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
'') cfg.machines);
};
}

View File

@@ -1,45 +0,0 @@
--- a/drivers/gpu/drm/panel/panel-cwu50.c
+++ b/drivers/gpu/drm/panel/panel-cwu50.c
@@ -27,12 +27,12 @@ static const struct drm_display_mode default_mode = {
.clock = 61020,
- .hdisplay = 1280,
- .hsync_start = 1280 + 8,
- .hsync_end = 1280 + 8 + 2,
- .htotal = 1280 + 8 + 2 + 16,
- .vdisplay = 720,
- .vsync_start = 720 + 30,
- .vsync_end = 720 + 30 + 15,
- .vtotal = 720 + 30 + 15 + 15,
+ .hdisplay = 720,
+ .hsync_start = 720 + 30,
+ .hsync_end = 720 + 30 + 15,
+ .htotal = 720 + 30 + 15 + 15,
+ .vdisplay = 1280,
+ .vsync_start = 1280 + 8,
+ .vsync_end = 1280 + 8 + 2,
+ .vtotal = 1280 + 8 + 2 + 16,
};
static inline struct cwu50 *panel_to_cwu50(struct drm_panel *panel)
@@ -586,7 +586,8 @@ static int cwu50_init_sequence2(struct cwu50 *ctx)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
- int err;
+ int err;
+ u8 buf[4];
dcs_write_seq(0xE0,0x00);
@@ -633,6 +634,12 @@ static int cwu50_init_sequence2(struct cwu50 *ctx)
dcs_write_seq(0x11);// SLPOUT
msleep (200);
+ dcs_write_seq(0xE0,0x00);
+ mipi_dsi_dcs_read(dsi, 0x04, buf, 3);
+
+ if(buf[0] == 0x39) ctx->is_new_panel = 1;
+
+ dcs_write_seq(0xE0,0x00);
dcs_write_seq(0x29);// DSiPON
msleep (100);

13
users/builder.nix Normal file
View File

@@ -0,0 +1,13 @@
{ config, lib, pkgs, keys, ... }: {
users.users.builder = {
isSystemUser = true;
group = "builder";
home = "/var/empty";
createHome = false;
shell = pkgs.nologin;
openssh.authorizedKeys.keys = with keys; [
users.builder.main
];
};
users.groups.builder = {};
}