mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-27 00:06:16 +02:00
209 lines
5.6 KiB
Perl
209 lines
5.6 KiB
Perl
|
#! @perl@ -w
|
||
|
|
||
|
use strict;
|
||
|
use Fcntl ':flock';
|
||
|
use English '-no_match_vars';
|
||
|
|
||
|
# General operation:
|
||
|
#
|
||
|
# Try to find a free machine of type $neededSystem. We do this as
|
||
|
# follows:
|
||
|
# - We acquire an exclusive lock on $currentLoad/main-lock.
|
||
|
# - For each machine $machine of type $neededSystem and for each $slot
|
||
|
# less than the maximum load for that machine, we try to get an
|
||
|
# exclusive lock on $currentLoad/$machine-$slot (without blocking).
|
||
|
# If we get such a lock, we send "accept" to the caller. Otherwise,
|
||
|
# we send "postpone" and exit.
|
||
|
# - We release the exclusive lock on $currentLoad/main-lock.
|
||
|
# - We perform the build on $neededSystem.
|
||
|
# - We release the exclusive lock on $currentLoad/$machine-$slot.
|
||
|
#
|
||
|
# The nice thing about this scheme is that if we die prematurely, the
|
||
|
# locks are released automatically.
|
||
|
|
||
|
my $loadIncreased = 0;
|
||
|
|
||
|
my $amWilling = shift @ARGV;
|
||
|
my $localSystem = shift @ARGV;
|
||
|
my $neededSystem = shift @ARGV;
|
||
|
my $drvPath = shift @ARGV;
|
||
|
|
||
|
sub sendReply {
|
||
|
my $reply = shift;
|
||
|
open OUT, ">&3" or die;
|
||
|
print OUT "$reply\n";
|
||
|
close OUT;
|
||
|
}
|
||
|
|
||
|
sub decline {
|
||
|
sendReply "decline";
|
||
|
exit 0;
|
||
|
}
|
||
|
|
||
|
my $currentLoad = $ENV{"NIX_CURRENT_LOAD"};
|
||
|
decline unless defined $currentLoad;
|
||
|
mkdir $currentLoad, 0777 or die unless -d $currentLoad;
|
||
|
|
||
|
my $conf = $ENV{"NIX_REMOTE_SYSTEMS"};
|
||
|
decline if !defined $conf || ! -e $conf;
|
||
|
|
||
|
# Decline if the local system can do the build.
|
||
|
decline if $amWilling && ($localSystem eq $neededSystem);
|
||
|
|
||
|
|
||
|
# Otherwise find a willing remote machine.
|
||
|
my %machines;
|
||
|
my %systemTypes;
|
||
|
my %sshKeys;
|
||
|
my %maxJobs;
|
||
|
my %curJobs;
|
||
|
|
||
|
|
||
|
# Read the list of machines.
|
||
|
open CONF, "< $conf" or die;
|
||
|
|
||
|
while (<CONF>) {
|
||
|
chomp;
|
||
|
s/\#.*$//g;
|
||
|
next if /^\s*$/;
|
||
|
/^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\d+)\s*$/ or die;
|
||
|
$machines{$1} = "";
|
||
|
$systemTypes{$1} = $2;
|
||
|
$sshKeys{$1} = $3;
|
||
|
$maxJobs{$1} = $4;
|
||
|
}
|
||
|
|
||
|
close CONF;
|
||
|
|
||
|
|
||
|
# Acquire the exclusive lock on $currentLoad/main-lock.
|
||
|
my $mainLock = "$currentLoad/main-lock";
|
||
|
open MAINLOCK, ">>$mainLock" or die;
|
||
|
flock(MAINLOCK, LOCK_EX) or die;
|
||
|
|
||
|
|
||
|
# Find a suitable system.
|
||
|
my $rightType = 0;
|
||
|
my $machine;
|
||
|
LOOP: foreach my $cur (keys %machines) {
|
||
|
if ($neededSystem eq $systemTypes{$cur}) {
|
||
|
$rightType = 1;
|
||
|
|
||
|
# We have a machine of the right type. Try to get a lock on
|
||
|
# one of the machine's lock files.
|
||
|
my $slot = 0;
|
||
|
while ($slot < $maxJobs{$cur}) {
|
||
|
my $slotLock = "$currentLoad/$cur-$slot";
|
||
|
open SLOTLOCK, ">>$slotLock" or die;
|
||
|
if (flock(SLOTLOCK, LOCK_EX | LOCK_NB)) {
|
||
|
$machine = $cur;
|
||
|
last LOOP;
|
||
|
}
|
||
|
close SLOTLOCK;
|
||
|
$slot++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
close MAINLOCK;
|
||
|
|
||
|
|
||
|
# Didn't find one?
|
||
|
if (!defined $machine) {
|
||
|
if ($rightType) {
|
||
|
sendReply "postpone";
|
||
|
exit 0;
|
||
|
} else {
|
||
|
decline;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Yes we did, accept.
|
||
|
sendReply "accept";
|
||
|
open IN, "<&4" or die;
|
||
|
my $x = <IN>;
|
||
|
chomp $x;
|
||
|
#print "got $x\n";
|
||
|
close IN;
|
||
|
|
||
|
if ($x ne "okay") {
|
||
|
exit 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
# Do the actual job.
|
||
|
print "BUILDING REMOTE: $drvPath on $machine\n";
|
||
|
|
||
|
# Make sure that we don't get any SSH passphrase or host key popups -
|
||
|
# if there is any problem it should fail, not do something
|
||
|
# interactive.
|
||
|
$ENV{"DISPLAY"} = "";
|
||
|
$ENV{"SSH_PASSWORD_FILE="} = "";
|
||
|
$ENV{"SSH_ASKPASS="} = "";
|
||
|
|
||
|
my $sshOpts = "-i $sshKeys{$machine} -x";
|
||
|
|
||
|
# Hack to support Cygwin: if we login without a password, we don't
|
||
|
# have exactly the same right as when we do. This causes the
|
||
|
# Microsoft C compiler to fail with certain flags:
|
||
|
#
|
||
|
# http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=99676
|
||
|
#
|
||
|
# So as a workaround, we pass a verbatim password. ssh tries to makes
|
||
|
# this very hard; the trick is to make it call SSH_ASKPASS to get the
|
||
|
# password. (It only calls this command when there is no controlling
|
||
|
# terminal, but Nix ensures that is is the case. When doing this
|
||
|
# manually, use setsid(1).)
|
||
|
if ($sshKeys{$machine} =~ /^password:/) {
|
||
|
my $passwordFile = $sshKeys{$machine};
|
||
|
$passwordFile =~ s/^password://;
|
||
|
$sshOpts = "ssh -x";
|
||
|
$ENV{"SSH_PASSWORD_FILE"} = $passwordFile;
|
||
|
$ENV{"SSH_ASKPASS"} = "/tmp/writepass";
|
||
|
|
||
|
open WRITEPASS, ">/tmp/writepass" or die;
|
||
|
print WRITEPASS "#! /bin/sh\ncat \"\$SSH_PASSWORD_FILE\"";
|
||
|
close WRITEPASS;
|
||
|
chmod 0755, "/tmp/writepass" or die;
|
||
|
}
|
||
|
|
||
|
my $inputs = `cat inputs`; die if ($? != 0);
|
||
|
$inputs =~ s/\n/ /g;
|
||
|
|
||
|
my $outputs = `cat outputs`; die if ($? != 0);
|
||
|
$outputs =~ s/\n/ /g;
|
||
|
|
||
|
print "COPYING INPUTS...\n";
|
||
|
|
||
|
my $maybeSign = "";
|
||
|
$maybeSign = "--sign" if -e "/nix/etc/nix/signing-key.sec";
|
||
|
|
||
|
system("NIX_SSHOPTS=\"$sshOpts\" nix-copy-closure $machine $maybeSign $drvPath $inputs") == 0
|
||
|
or die "cannot copy inputs to $machine: $?";
|
||
|
|
||
|
print "BUILDING...\n";
|
||
|
|
||
|
system("ssh $sshOpts $machine 'nix-store -rvvK $drvPath'") == 0
|
||
|
or die "remote build on $machine failed: $?";
|
||
|
|
||
|
print "REMOTE BUILD DONE: $drvPath on $machine\n";
|
||
|
|
||
|
foreach my $output (split '\n', $outputs) {
|
||
|
my $maybeSignRemote = "";
|
||
|
$maybeSignRemote = "--sign" if $UID != 0;
|
||
|
|
||
|
system("ssh $sshOpts $machine 'nix-store --export $maybeSignRemote $output' > dump") == 0
|
||
|
or die "cannot copy $output from $machine: $?";
|
||
|
|
||
|
# This doesn't work yet, since the caller has a lock on the output
|
||
|
# path. We should move towards lock-free invocation of build
|
||
|
# hooks and substitutes.
|
||
|
#system("nix-store --import < dump") == 0
|
||
|
# or die "cannot import $output: $?";
|
||
|
|
||
|
# Hack: skip the first 8 bytes (the nix-store --export next
|
||
|
# archive marker). The archive follows.
|
||
|
system("(dd bs=1 count=8 of=/dev/null && cat) < dump | nix-store --restore $output") == 0
|
||
|
or die "cannot restore $output: $?";
|
||
|
}
|