Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@
"@cloudflare/vite-plugin"
],
"groupName": "cloudflare-workers-tooling"
},
{
"description": "conventional-changelog-conventionalcommits@10 requires conventional-changelog-writer@9, which semantic-release@25's bundled @semantic-release/release-notes-generator@14 does not resolve; hold on 9.x until semantic-release ships a compatible generator",
"matchPackageNames": [
"conventional-changelog-conventionalcommits"
],
"allowedVersions": "<10"
}
]
}
8 changes: 3 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 3 additions & 7 deletions bun.nix
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,6 @@
url = "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz";
hash = "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==";
};
"@conventional-changelog/template@1.4.0" = fetchurl {
url = "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.4.0.tgz";
hash = "sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==";
};
"@cspotcode/source-map-support@0.8.1" = fetchurl {
url = "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz";
hash = "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==";
Expand Down Expand Up @@ -1562,9 +1558,9 @@
url = "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.1.0.tgz";
hash = "sha512-GGf2Nipn1RUCAktxuVauVr1e3r8QrLP/B0lEUsFktmGqc3ddbQkhoJZHJctVU829U1c6mTSWftrVOCHaL85Q3w==";
};
"conventional-changelog-conventionalcommits@10.4.0" = fetchurl {
url = "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-10.4.0.tgz";
hash = "sha512-Rriac6ZrAlVm6cy9Bz4NSp+WMHpwNXoPIYex+HjCgduAVUSbnew29DQjQw0C4g9u3HtSYzGiGY+pdBXAZo+4aA==";
"conventional-changelog-conventionalcommits@9.3.1" = fetchurl {
url = "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.1.tgz";
hash = "sha512-dTYtpIacRpcZgrvBYvBfArMmK2xvIpv2TaxM0/ZI5CBtNUzvF2x0t15HsbRABWprS6UPmvj+PzHVjSx4qAVKyw==";
};
"conventional-changelog-writer@8.2.0" = fetchurl {
url = "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.2.0.tgz";
Expand Down
46 changes: 46 additions & 0 deletions modules/checks/hooks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,52 @@
exit 1
fi

touch $out
'';

checks.updater-repository-paths =
pkgs.runCommand "updater-repository-paths"
{
nativeBuildInputs = [ pkgs.ripgrep ];
updaters = ../../pkgs/by-name;
}
''
set -uo pipefail
failures=0
identity_pattern='BASH_SOURCE|[$][{]?0([^0-9]|$)'

for updater in "$updaters"/*/update.sh; do
[ -f "$updater" ] || continue
package="''${updater%/update.sh}/package.nix"

if matches=$(rg -n "$identity_pattern" "$updater"); then
relative_path="''${updater#"$updaters"/}"
echo "FAIL: $relative_path derives package paths from its copied updater identity:" >&2
echo "$matches" >&2
failures=$((failures + 1))
fi

if rg -q 'git rev-parse --show-toplevel' "$updater" \
&& ! sed -n '1,2p' "$updater" | rg -q '#!nix-shell.*[[:space:]]git([[:space:]]|$)'; then
relative_path="''${updater#"$updaters"/}"
echo "FAIL: $relative_path resolves the repository with git but does not declare git in its nix-shell packages." >&2
failures=$((failures + 1))
fi

if [ -f "$package" ] \
&& rg -q 'outputHash[[:space:]]*=' "$package" \
&& ! rg -q 'outputHash' "$updater"; then
relative_path="''${updater#"$updaters"/}"
echo "FAIL: $relative_path does not refresh its package's fixed-output hash." >&2
failures=$((failures + 1))
fi
done

if [ "$failures" -ne 0 ]; then
echo "Updater scripts must resolve the repository root with 'git rev-parse --show-toplevel', then address pkgs/by-name/<package> under that root." >&2
exit 1
fi

touch $out
'';
};
Expand Down
115 changes: 115 additions & 0 deletions modules/checks/stibnite-access-wiring.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Structural check for stibnite's aarch64-darwin build access.
#
# It guards the pairing between two machines' configurations, which no single
# machine's evaluation can catch. magnetite generates two keypairs and stibnite
# authorizes them under two different accounts; swapping them, or authorizing
# the build key without its forced command, produces a configuration that
# builds and activates and is wrong in the one way that matters — the build
# key would carry an interactive login, or the independently revocable
# identities would be assigned to the wrong accounts.
#
# It also pins the two mechanisms to one account: the nix.buildMachines entry
# (result copied back to the caller) and the remote-store URI (build stays in
# stibnite's store) must name the same user and the same ssh alias, or one of
# them silently reaches an account that authorizes nothing.
{
self,
lib,
...
}:
{
perSystem =
{ pkgs, system, ... }:
let
mkCheck = self.lib.mkStructuralCheck pkgs;

magnetite = self.nixosConfigurations.magnetite.config;
stibnite = self.darwinConfigurations.stibnite.config;

buildKey =
lib.removeSuffix "\n"
magnetite.clan.core.vars.generators.stibnite-nix-build.files."key.pub".value;
sessionKey =
lib.removeSuffix "\n"
magnetite.clan.core.vars.generators.stibnite-agent-session.files."key.pub".value;

authorized = user: stibnite.environment.etc."ssh/nix_authorized_keys.d/${user}".text;
authorizedLines = user: builtins.filter (line: line != "") (lib.splitString "\n" (authorized user));

darwinEntries = builtins.filter (
m: builtins.elem "aarch64-darwin" m.systems
) magnetite.nix.buildMachines;
darwinEntry = builtins.head darwinEntries;

# The forced command names an activation-time store path, so the oracle
# asserts its shape: the key options, and that the program is the
# nix-daemon of the nix package stibnite activates.
buildLine = builtins.head (authorizedLines "nixbuild");
forcedCommand = ''restrict,command="${stibnite.nix.package}/bin/nix-daemon --stdio"'';
in
{
checks =
lib.optionalAttrs
(builtins.elem system [
"x86_64-linux"
"aarch64-darwin"
])
{
stibnite-access-wiring = mkCheck {
name = "stibnite-access-wiring";
actual = {
darwinBuilderCount = builtins.length darwinEntries;
darwinBuilder = {
inherit (darwinEntry)
hostName
sshUser
protocol
systems
maxJobs
supportedFeatures
;
};

# Both mechanisms, one account and one alias.
storeUriNamesBuildAccount =
magnetite.environment.etc."nix/stibnite-store-uri".text
== "ssh-ng://${darwinEntry.sshUser}@${darwinEntry.hostName}?ssh-key=${darwinEntry.sshKey}\n";

# Key separation, asserted in both directions.
buildAccountAuthorizes = authorizedLines "nixbuild";
sessionKeyIsNotABuildKey = !(lib.hasInfix sessionKey (authorized "nixbuild"));
buildKeyIsNotASessionKey = !(lib.hasInfix buildKey (authorized "crs58"));
sessionKeyAuthorizedForSessions = lib.hasInfix sessionKey (authorized "crs58");

# The build key carries a forced command and nothing else.
buildLineIsRestrictedToTheProtocol = buildLine == "${forcedCommand} ${buildKey}";

buildAccountIsTrusted = builtins.elem "nixbuild" stibnite.nix.settings.trusted-users;
buildAccountIsManaged = builtins.elem "nixbuild" stibnite.users.knownUsers;
};
expected = {
darwinBuilderCount = 1;
darwinBuilder = {
hostName = "stibnite-builder";
sshUser = "nixbuild";
protocol = "ssh-ng";
systems = [ "aarch64-darwin" ];
maxJobs = 4;
supportedFeatures = [
"apple-virt"
"big-parallel"
];
};
storeUriNamesBuildAccount = true;
buildAccountAuthorizes = [ "${forcedCommand} ${buildKey}" ];
sessionKeyIsNotABuildKey = true;
buildKeyIsNotASessionKey = true;
sessionKeyAuthorizedForSessions = true;
buildLineIsRestrictedToTheProtocol = true;
buildAccountIsTrusted = true;
buildAccountIsManaged = true;
};
};
};
};
}
12 changes: 12 additions & 0 deletions modules/home/ai/omp/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@
# aiAgentSettings.theme carries, which pi and atomic resolve against a
# vendored theme file; the two are not interchangeable strings.
theme.dark = lib.mkDefault "dark-catppuccin";

# 180000 rather than upstream's 30000: a clone's first export has to
# realise the flake devshell, 7.4s here against a populated nix store
# and minutes when the closure must be built, and overrunning the
# budget runs the command without the devshell rather than failing.
# direnv is pinned at its own default so a change to that default
# shows up as a diff here.
bash = {
direnv = lib.mkDefault "auto";

direnvLoadTimeoutMs = lib.mkDefault 180000;
};
modelRoles = {
default = lib.mkDefault "anthropic/claude-opus-5:xhigh";
advisor = lib.mkDefault "openai-codex/gpt-5.6-sol:xhigh";
Expand Down
22 changes: 22 additions & 0 deletions modules/machines/darwin/stibnite/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ in
zt-dns
zt-services-trust
magnetite-builder
stibnite-build-host
# Not importing users module (defines testuser at UID 550)
# stibnite defines its own user (crs58)
]);
Expand Down Expand Up @@ -224,6 +225,27 @@ in
# Offload native x86_64-linux builds to magnetite over ZeroTier.
services.magnetite-builder.enable = true;

# Inbound side of the same asymmetry: stibnite is the fleet's only
# aarch64-darwin machine, so it serves darwin builds to hosts that cannot
# perform them. The two keypairs come from magnetite and are authorized
# separately for independent revocation and rotation. The build key is
# confined at the SSH boundary to the nix protocol. The session key is
# broader: it logs in as admin-group crs58, a Nix trusted user, so it has
# build authority plus shell access.
# Both encrypted private halves and public values are committed under
# vars/per-machine/magnetite/. `clan vars generate magnetite` populates
# them; only magnetite and authorized users can decrypt the private
# halves.
services.stibnite-build-host = {
enable = true;
buildKeys = [
inputs.self.nixosConfigurations.magnetite.config.clan.core.vars.generators.stibnite-nix-build.files."key.pub".value
];
sessionKeys = [
inputs.self.nixosConfigurations.magnetite.config.clan.core.vars.generators.stibnite-agent-session.files."key.pub".value
];
};

# Colima for OCI container management (complementary to nix-rosetta-builder)
services.colima = {
enable = true;
Expand Down
39 changes: 37 additions & 2 deletions modules/machines/nixos/magnetite/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ in
hm-sops-bridge
niks3
ssh-known-hosts
stibnite-builder
stibnite-session
buildbot
nixbot
gitea
Expand Down Expand Up @@ -82,6 +84,30 @@ in
];
};

# magnetite is x86_64-linux and cannot build aarch64-darwin derivations,
# so darwin work is dispatched to stibnite, the fleet's only machine of
# that system. Two mechanisms, two callers:
# nix.buildMachines below — a local nix build by an operator who wants
# the darwin result in magnetite's store.
# /etc/nix/stibnite-store-uri — a caller that wants the build to happen
# entirely in stibnite's store with nothing copied back.
# nixbot.toml sets attribute = "checks.x86_64-linux", which prevents CI
# from evaluating or requesting aarch64-darwin work and makes this builder
# unreachable from CI. modules/nixos/nixbot.nix and
# modules/nixos/buildbot.nix each set buildSystems = [ "x86_64-linux" ]
# as an independent second layer. These controls remain because stibnite
# is a laptop without guaranteed availability and a sleeping machine
# could gate CI.
services.stibnite-builder.enable = true;
nix.buildMachines = config.services.stibnite-builder.buildMachines;

# The build and session keys are separately authorized for independent
# revocation and rotation. Only the build key is confined to the Nix
# protocol by `restrict` and a forced command. The session key is broader:
# it grants an unrestricted login as admin-group crs58, already a Nix
# trusted user, with build authority plus shell.
services.stibnite-session.enable = true;

# User configuration managed via clan inventory users service (modules/clan/inventory/services/users/cameron.nix).

security.sudo.wheelNeedsPassword = false;
Expand Down Expand Up @@ -396,8 +422,17 @@ in
users.users.builder = {
isNormalUser = true;
description = "Remote nix build user";
openssh.authorizedKeys.keys = inputs.self.users.crs58.meta.sshKeys ++ [
inputs.self.darwinConfigurations.stibnite.config.clan.core.vars.generators.nix-remote-build.files."key.pub".value
# nix-daemon --stdio is exactly what an ssh-ng caller would otherwise
# invoke (`remote-program` defaults to nix-daemon), so forcing it serves
# the build protocol and discards anything else the client asks for.
# sshd runs the forced command through the account's login shell, so the
# shell must stay executable; a nologin shell would break the protocol
# rather than harden it. Mirrors the stibnite direction of this pair.
openssh.authorizedKeys.keys = [
''restrict,command="${config.nix.package}/bin/nix-daemon --stdio" ${
lib.removeSuffix "\n"
inputs.self.darwinConfigurations.stibnite.config.clan.core.vars.generators.nix-remote-build.files."key.pub".value
}''
];
};

Expand Down
Loading
Loading