Skip to content

drv-daemon: ad-hoc Builds + queue-runner handoff#1692

Draft
amaanq wants to merge 4 commits into
NixOS:masterfrom
obsidiansystems:drv-daemon
Draft

drv-daemon: ad-hoc Builds + queue-runner handoff#1692
amaanq wants to merge 4 commits into
NixOS:masterfrom
obsidiansystems:drv-daemon

Conversation

@amaanq

@amaanq amaanq commented Apr 30, 2026

Copy link
Copy Markdown
Member

Motivation

Hydra currently builds import-from-derivation work on the evaluator host. Those builds bypass the queue runner, builder fleet, Hydra build history, and the normal logs/operators use to debug failures.

Solution

This PR adds hydra-drv-daemon, a Nix daemon protocol service that turns evaluator build requests into ad-hoc Hydra builds. When configured, hydra-evaluator points IFD-capable child processes at this daemon via NIX_REMOTE, so IFDs are built by the normal queue-runner / builder path instead of locally on the evaluator.

The daemon inserts an internal adhoc/adhoc build row, notifies the queue runner, waits for build_finished, and returns the completed build result to the Nix client. Read-side store operations and uploads are proxied to the upstream nix-daemon.

@Ericson2314 Ericson2314 Apr 30, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be called this, dynamic derivations and import-from-derivation are completely separate features.

We want one test for "ad hoc jobs" and one test for a regular job (like with the project/jobset stuff) but using import from derivation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this test isn't using content-addressing (on purpose, because IFD is not experimental)

Comment on lines +11 to +16
my $ctx = test_context(
nix_config => qq|
experimental-features = ca-derivations dynamic-derivations
|,
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
my $ctx = test_context(
nix_config => qq|
experimental-features = ca-derivations dynamic-derivations
|,
);

Comment on lines +212 to +234
sub _spawn_builder {
my ($self) = @_;
my $ctx = $self->{ctx};

$self->_spawn(
builder => "Builder",
[
"hydra-builder",
"--gateway-endpoint", "http://[::1]:$self->{grpc_port}",
],
env => {
NIX_REMOTE => $ctx->{builder}{nix_store_uri},
NIX_CONF_DIR => $ctx->{builder}{nix_conf_dir},
NIX_STATE_DIR => $ctx->{builder}{nix_state_dir},
NIX_STORE_DIR => $ctx->{builder}{nix_store_dir},
RUST_LOG => "hydra_builder=debug,info",
},
);

_wait_for($self->{ua}, "$self->{base_url}/status/machines", sub {
shift->decoded_content =~ /"hostname"/;
}) or die "Timed out waiting for builder to register\n";
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very sus! we already have code to start the builder, we should not need new such code.

Comment on lines +19 to +34
sub _get_random_port {
my ($min, $max) = @_;
while (1) {
my $port = $min + int(rand($max - $min + 1));
my $sock = IO::Socket::IP->new(
LocalAddr => '::',
LocalPort => $port,
Proto => 'tcp',
ReuseAddr => 0,
);
if ($sock) {
close($sock);
return $port;
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be shared with other htings

@Ericson2314

Copy link
Copy Markdown
Member

I think it would be best to be able to run the entire test suite (or at least the parts doing evaluation) with and without the new drv daemon.

That means that the new IFD and ad hoc jobs tests would require the new daemon, but every other test would run twice in both modes.

Comment on lines +303 to +312
// unix:// ignores NIX_STORE_DIR, so NIX_REMOTE must include the logical store dir.
auto drvDaemonSocket = config->getStrOption("drv_daemon_socket");
auto nixStoreDir = config->getStrOption("nix_store_dir", "/nix/store");

jobset.pid = startProcess([&]() {
if (!drvDaemonSocket.empty()) {
auto remote = "unix://" + urlEncodePath(drvDaemonSocket)
+ "?store=" + urlEncodePath(nixStoreDir);
setenv("NIX_REMOTE", remote.c_str(), 1);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure, but this feels like the wrong layer of abstraction. I forgot that hydra-evaluator was its own separate demon, not one of the "scripts".
What that means is that:

  1. If the setting stays in the config, it should be holistic --- do we want evaluations to use the drv daemon or not? That is the actual policy choice here.
  2. If we instead mess around with the underlying mechanisms, we could just set NIX_REMOTE for the entire hydra-evaluator from the outside, not within it. I.e. it is the responsibility of however the daemons set up and run to wire things up correctly.

Comment on lines +169 to +188
/* Encode path/query separators before embedding operator paths in a unix:// store URL. */
static std::string urlEncodePath(const std::string & s)
{
std::string out;
out.reserve(s.size());
for (unsigned char c : s) {
bool safe = std::isalnum(c)
|| c == '-' || c == '.' || c == '_' || c == '~'
|| c == '/' || c == ':';
if (safe) {
out.push_back(static_cast<char>(c));
} else {
char buf[4];
std::snprintf(buf, sizeof(buf), "%%%02X", c);
out.append(buf);
}
}
return out;
}

@Ericson2314 Ericson2314 Apr 30, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per b5e42d4#r3171126384 we might not even need this (because the environment variables are set externally and not in this C++, option 2).

If we got with option 1, I think we can replace this with a Nix function, not hand-roll it.

Comment thread nixos-modules/drv-daemon-module.nix Outdated
Logical store directory. Must match the store dir used by the
queue runner / builder; the value is also what the evaluator
appends as `?store=<dir>` when it points `NIX_REMOTE` at the
socket, since `unix://` connections ignore `NIX_STORE_DIR`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that is true, I don't think they ignore NIX_STORE_DIR

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But also, we much rather not rely on NIX_STORE_DIR and just use NIX_REMOTE, so maybe this fine.

Comment thread nixos-modules/drv-daemon-module.nix Outdated
Comment on lines +54 to +64
routeEvaluatorIfds = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
When true, wire hydra-evaluator to this daemon and order it after hydra-drv-daemon.service.

Note: routing only takes effect when
`allowImportFromDerivation` is also enabled — otherwise
`nix-eval-jobs` rejects every IFD before the daemon can see it.
'';
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why this is a setting for hydra-drv-daemon itself. That seems wrong --- why does it care who is using it and for what purpose?

Comment thread nixos-modules/drv-daemon-module.nix Outdated
Comment on lines +66 to +72
allowImportFromDerivation = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
When true, enable IFD evaluation in hydra.conf. This lets untrusted evals run builders, so it stays opt-in.
'';
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

@Ericson2314 Ericson2314 marked this pull request as draft May 2, 2026 20:39
@amaanq amaanq force-pushed the drv-daemon branch 4 times, most recently from ad5b267 to 5f6e67f Compare May 4, 2026 21:26
Ericson2314 and others added 4 commits May 6, 2026 13:49
Port hydra-evaluator from C++ to Rust, removing the last C++ executable
from the project. The new implementation uses `sqlx` and `tokio`, matching
the async patterns already established by hydra-queue-runner and
hydra-builder.

Build/packaging changes:
- Remove all C++ dependencies from `subprojects/hydra/meson.build`
- Add `subprojects/hydra-evaluator/package.nix` following the same
  per-binary pattern as `hydra-queue-runner` and `hydra-builder`: each
  Rust binary is its own derivation that builds against a fileset
  containing only its own crate, and `postPatch` strips the other Rust
  binary crates from the workspace `members` list so cargo doesn't try
  to load their (absent) manifests. Properly sharing the dep crates
  between these builds is left for later.
- Add `evaluatorExecutable` option to the NixOS web-app module, matching
  how the queue runner and builder are discovered via their own modules
- Clean up stale references in `hydra-tests/meson.build` and
  `dev-shell.nix`

`JobsetRow` uses `i32` for `lastCheckedTime` and `triggerTime` to match
the `INT4` Postgres schema (`sqlx` refuses to decode `INT4` into `i64`).
Post-eval DB updates are wrapped in a transaction, as in the C++
original. The DBI parser supports `sslmode`/`sslrootcert`/`sslcert`/
`sslkey` that `libpq` understood natively in the C++ version; unknown
parameters are warned about rather than fatal.

Satisfy the crate's own `#![deny(clippy::pedantic)]` gate:
- use `fs_err` per workspace `disallowed_methods` policy
- replace `as`-casts with `try_from` (schema is `INT4`, saturate at `i32::MAX`)
- collapse nested `if let` chains

Co-authored-by: Jörg Thalheim <joerg@thalheim.io>
Co-authored-by: Claude <noreply@anthropic.com>
Add hydra-drv-daemon, a nix-daemon protocol endpoint that turns build requests into ad-hoc Hydra Builds and waits for queue-runner completion.

Read operations and store uploads are proxied to the upstream nix-daemon.
When drv_daemon_socket is configured, hydra-evaluator runs jobset evaluators with NIX_REMOTE pointing at hydra-drv-daemon.

Add the NixOS service/module and dev Procfile wiring.
Add an end-to-end test stack for submitting a derivation through hydra-drv-daemon and building it through queue-runner/builder.

The fixture is input-addressed because CA realisations are not surfaced yet.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants