2016-09-26 16:06:51 +00:00
|
|
|
|
#! /usr/bin/env perl
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
|
|
|
|
use strict;
|
2016-09-26 16:06:51 +00:00
|
|
|
|
use warnings;
|
2016-03-01 19:00:06 +00:00
|
|
|
|
use Data::Dumper;
|
2016-09-29 09:50:14 +00:00
|
|
|
|
use Digest::SHA;
|
2016-03-01 19:00:06 +00:00
|
|
|
|
use Fcntl qw(:flock);
|
|
|
|
|
use File::Basename;
|
|
|
|
|
use File::Path;
|
|
|
|
|
use File::Slurp;
|
2016-09-29 09:50:14 +00:00
|
|
|
|
use File::stat;
|
2016-03-01 19:00:06 +00:00
|
|
|
|
use JSON::PP;
|
|
|
|
|
use LWP::UserAgent;
|
2016-03-03 14:59:21 +00:00
|
|
|
|
use List::MoreUtils qw(uniq);
|
2016-09-26 16:06:51 +00:00
|
|
|
|
use Net::Amazon::S3;
|
2016-09-29 09:50:14 +00:00
|
|
|
|
use POSIX qw(strftime);
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
2016-03-04 19:45:35 +00:00
|
|
|
|
my $channelName = $ARGV[0];
|
|
|
|
|
my $releaseUrl = $ARGV[1];
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
2016-09-26 16:06:51 +00:00
|
|
|
|
die "Usage: $0 CHANNEL-NAME RELEASE-URL\n" unless defined $channelName && defined $releaseUrl;
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
2016-03-04 19:45:35 +00:00
|
|
|
|
$channelName =~ /^([a-z]+)-(.*)$/ or die;
|
|
|
|
|
my $channelDirRel = $channelName eq "nixpkgs-unstable" ? "nixpkgs" : "$1/$2";
|
2016-09-26 16:06:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Configuration.
|
2019-01-24 22:25:12 +00:00
|
|
|
|
my $TMPDIR = $ENV{'TMPDIR'} // "/tmp";
|
|
|
|
|
my $channelsDir = "/home/hydra-mirror/channels";
|
|
|
|
|
my $filesCache = "${TMPDIR}/nixos-files.sqlite";
|
2016-09-26 16:06:51 +00:00
|
|
|
|
my $bucketName = "nix-releases";
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
2016-03-03 17:29:05 +00:00
|
|
|
|
$ENV{'GIT_DIR'} = "/home/hydra-mirror/nixpkgs-channels";
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
2016-09-26 16:06:51 +00:00
|
|
|
|
|
|
|
|
|
# S3 setup.
|
|
|
|
|
my $aws_access_key_id = $ENV{'AWS_ACCESS_KEY_ID'} or die;
|
|
|
|
|
my $aws_secret_access_key = $ENV{'AWS_SECRET_ACCESS_KEY'} or die;
|
|
|
|
|
|
|
|
|
|
my $s3 = Net::Amazon::S3->new(
|
|
|
|
|
{ aws_access_key_id => $aws_access_key_id,
|
|
|
|
|
aws_secret_access_key => $aws_secret_access_key,
|
|
|
|
|
retry => 1,
|
|
|
|
|
host => "s3-eu-west-1.amazonaws.com",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
my $bucket = $s3->bucket($bucketName) or die;
|
|
|
|
|
|
|
|
|
|
|
2016-03-01 19:00:06 +00:00
|
|
|
|
sub fetch {
|
|
|
|
|
my ($url, $type) = @_;
|
|
|
|
|
|
|
|
|
|
my $ua = LWP::UserAgent->new;
|
|
|
|
|
$ua->default_header('Accept', $type) if defined $type;
|
|
|
|
|
|
|
|
|
|
my $response = $ua->get($url);
|
|
|
|
|
die "could not download $url: ", $response->status_line, "\n" unless $response->is_success;
|
|
|
|
|
|
|
|
|
|
return $response->decoded_content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $releaseInfo = decode_json(fetch($releaseUrl, 'application/json'));
|
|
|
|
|
|
|
|
|
|
my $releaseId = $releaseInfo->{id} or die;
|
|
|
|
|
my $releaseName = $releaseInfo->{nixname} or die;
|
|
|
|
|
my $evalId = $releaseInfo->{jobsetevals}->[0] or die;
|
|
|
|
|
my $evalUrl = "https://hydra.nixos.org/eval/$evalId";
|
|
|
|
|
my $evalInfo = decode_json(fetch($evalUrl, 'application/json'));
|
2016-09-26 16:06:51 +00:00
|
|
|
|
my $releasePrefix = "$channelDirRel/$releaseName";
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
|
|
|
|
my $rev = $evalInfo->{jobsetevalinputs}->{nixpkgs}->{revision} or die;
|
|
|
|
|
|
2016-09-26 16:06:51 +00:00
|
|
|
|
print STDERR "release is ‘$releaseName’ (build $releaseId), eval is $evalId, prefix is $releasePrefix, Git commit is $rev\n";
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
|
|
|
|
# Guard against the channel going back in time.
|
2019-01-24 22:25:12 +00:00
|
|
|
|
my @curReleaseUrl = split(/\//, read_file("$channelsDir/$channelName", err_mode => 'quiet') // "");
|
|
|
|
|
my $curRelease = pop @curReleaseUrl;
|
2020-01-03 02:45:51 +00:00
|
|
|
|
$! = 0; # Clear errno to avoid reporting non-fork/exec-related issues
|
2019-01-24 22:00:11 +00:00
|
|
|
|
my $d = `NIX_PATH= nix-instantiate --eval -E "builtins.compareVersions (builtins.parseDrvName \\"$curRelease\\").version (builtins.parseDrvName \\"$releaseName\\").version"`;
|
2020-01-03 02:45:51 +00:00
|
|
|
|
if ($? != 0) {
|
|
|
|
|
warn "Could not execute nix-instantiate: exit $?; errno $!\n";
|
|
|
|
|
exit 1;
|
2020-01-03 02:07:11 +00:00
|
|
|
|
}
|
2019-01-24 22:00:11 +00:00
|
|
|
|
chomp $d;
|
2019-11-15 16:01:22 +00:00
|
|
|
|
if ($d == 1) {
|
|
|
|
|
warn("channel would go back in time from $curRelease to $releaseName, bailing out\n");
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-24 22:25:12 +00:00
|
|
|
|
exit if $d == 0;
|
2019-01-24 22:00:11 +00:00
|
|
|
|
|
|
|
|
|
if ($bucket->head_key("$releasePrefix")) {
|
2016-03-01 19:00:06 +00:00
|
|
|
|
print STDERR "release already exists\n";
|
|
|
|
|
} else {
|
2019-01-24 22:25:12 +00:00
|
|
|
|
my $tmpDir = "$TMPDIR/release-$channelName/$releaseName";
|
2016-03-01 19:00:06 +00:00
|
|
|
|
File::Path::make_path($tmpDir);
|
|
|
|
|
|
|
|
|
|
write_file("$tmpDir/src-url", $evalUrl);
|
|
|
|
|
write_file("$tmpDir/git-revision", $rev);
|
|
|
|
|
write_file("$tmpDir/binary-cache-url", "https://cache.nixos.org");
|
|
|
|
|
|
2016-03-03 14:59:21 +00:00
|
|
|
|
if (! -e "$tmpDir/store-paths.xz") {
|
|
|
|
|
my $storePaths = decode_json(fetch("$evalUrl/store-paths", 'application/json'));
|
|
|
|
|
write_file("$tmpDir/store-paths", join("\n", uniq(@{$storePaths})) . "\n");
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-01 19:00:06 +00:00
|
|
|
|
sub downloadFile {
|
|
|
|
|
my ($jobName, $dstName) = @_;
|
|
|
|
|
|
|
|
|
|
my $buildInfo = decode_json(fetch("$evalUrl/job/$jobName", 'application/json'));
|
|
|
|
|
|
2018-02-06 15:45:13 +00:00
|
|
|
|
my $srcFile = $buildInfo->{buildproducts}->{1}->{path} or die "job '$jobName' lacks a store path";
|
2016-03-01 19:00:06 +00:00
|
|
|
|
$dstName //= basename($srcFile);
|
|
|
|
|
my $dstFile = "$tmpDir/" . $dstName;
|
|
|
|
|
|
2016-09-26 16:06:51 +00:00
|
|
|
|
my $sha256_expected = $buildInfo->{buildproducts}->{1}->{sha256hash} or die;
|
|
|
|
|
|
2016-03-01 19:00:06 +00:00
|
|
|
|
if (! -e $dstFile) {
|
|
|
|
|
print STDERR "downloading $srcFile to $dstFile...\n";
|
2018-06-06 22:11:16 +00:00
|
|
|
|
write_file("$dstFile.sha256", "$sha256_expected $dstName");
|
2016-03-01 19:00:06 +00:00
|
|
|
|
system("NIX_REMOTE=https://cache.nixos.org/ nix cat-store '$srcFile' > '$dstFile.tmp'") == 0
|
|
|
|
|
or die "unable to fetch $srcFile\n";
|
|
|
|
|
rename("$dstFile.tmp", $dstFile) or die;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-26 16:06:51 +00:00
|
|
|
|
if (-e "$dstFile.sha256") {
|
2019-04-30 22:24:33 +00:00
|
|
|
|
my $sha256_actual = `nix hash-file --base16 --type sha256 '$dstFile'`;
|
2016-09-26 16:06:51 +00:00
|
|
|
|
chomp $sha256_actual;
|
|
|
|
|
if ($sha256_expected ne $sha256_actual) {
|
|
|
|
|
print STDERR "file $dstFile is corrupt $sha256_expected $sha256_actual\n";
|
|
|
|
|
exit 1;
|
|
|
|
|
}
|
2016-03-01 19:00:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 19:45:35 +00:00
|
|
|
|
if ($channelName =~ /nixos/) {
|
|
|
|
|
downloadFile("nixos.channel", "nixexprs.tar.xz");
|
|
|
|
|
downloadFile("nixos.iso_minimal.x86_64-linux");
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
2016-03-04 19:45:35 +00:00
|
|
|
|
if ($channelName !~ /-small/) {
|
|
|
|
|
downloadFile("nixos.iso_minimal.i686-linux");
|
2020-01-27 22:02:19 +00:00
|
|
|
|
|
|
|
|
|
# Renamed iso_graphcial to iso_plasma5 in 20.03
|
|
|
|
|
if ($releaseName !~ /-19./) {
|
|
|
|
|
downloadFile("nixos.iso_plasma5.x86_64-linux");
|
|
|
|
|
} else {
|
|
|
|
|
downloadFile("nixos.iso_graphical.x86_64-linux");
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-04 19:45:35 +00:00
|
|
|
|
downloadFile("nixos.ova.x86_64-linux");
|
2016-03-17 11:21:13 +00:00
|
|
|
|
#downloadFile("nixos.ova.i686-linux");
|
2016-03-04 19:45:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
downloadFile("tarball", "nixexprs.tar.xz");
|
2016-03-01 19:00:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-07 15:12:25 +00:00
|
|
|
|
# Generate the programs.sqlite database and put it in
|
|
|
|
|
# nixexprs.tar.xz. Also maintain the debug info repository at
|
|
|
|
|
# https://cache.nixos.org/debuginfo.
|
2016-11-10 14:50:19 +00:00
|
|
|
|
if ($channelName =~ /nixos/ && -e "$tmpDir/store-paths") {
|
2016-08-11 12:41:26 +00:00
|
|
|
|
File::Path::make_path("$tmpDir/unpack");
|
|
|
|
|
system("tar", "xfJ", "$tmpDir/nixexprs.tar.xz", "-C", "$tmpDir/unpack") == 0 or die;
|
|
|
|
|
my $exprDir = glob("$tmpDir/unpack/*");
|
|
|
|
|
system("generate-programs-index $filesCache $exprDir/programs.sqlite http://nix-cache.s3.amazonaws.com/ $tmpDir/store-paths $exprDir/nixpkgs") == 0 or die;
|
2017-07-07 15:12:25 +00:00
|
|
|
|
system("index-debuginfo $filesCache s3://nix-cache $tmpDir/store-paths") == 0 or die;
|
2016-08-11 12:41:26 +00:00
|
|
|
|
system("rm -f $tmpDir/nixexprs.tar.xz $exprDir/programs.sqlite-journal") == 0 or die;
|
2016-09-26 16:06:51 +00:00
|
|
|
|
unlink("$tmpDir/nixexprs.tar.xz.sha256");
|
2016-08-11 12:41:26 +00:00
|
|
|
|
system("tar", "cfJ", "$tmpDir/nixexprs.tar.xz", "-C", "$tmpDir/unpack", basename($exprDir)) == 0 or die;
|
|
|
|
|
system("rm -rf $tmpDir/unpack") == 0 or die;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-e "$tmpDir/store-paths") {
|
|
|
|
|
system("xz", "$tmpDir/store-paths") == 0 or die;
|
|
|
|
|
}
|
2016-03-01 19:00:06 +00:00
|
|
|
|
|
2016-09-29 09:50:14 +00:00
|
|
|
|
my $now = strftime("%F %T", localtime);
|
|
|
|
|
my $title = "$channelName release $releaseName";
|
|
|
|
|
my $githubLink = "https://github.com/NixOS/nixpkgs-channels/commits/$rev";
|
|
|
|
|
|
|
|
|
|
my $html = "<html><head>";
|
|
|
|
|
$html .= "<title>$title</title></head>";
|
|
|
|
|
$html .= "<body><h1>$title</h1>";
|
|
|
|
|
$html .= "<p>Released on $now from <a href='$githubLink'>Git commit <tt>$rev</tt></a> ";
|
|
|
|
|
$html .= "via <a href='$evalUrl'>Hydra evaluation $evalId</a>.</p>";
|
2017-03-31 04:39:24 +00:00
|
|
|
|
$html .= "<table><thead><tr><th>File name</th><th>Size</th><th>SHA-256 hash</th></tr></thead><tbody>";
|
2016-09-29 09:50:14 +00:00
|
|
|
|
|
2016-09-26 16:06:51 +00:00
|
|
|
|
# Upload the release to S3.
|
2016-09-29 09:50:14 +00:00
|
|
|
|
for my $fn (sort glob("$tmpDir/*")) {
|
|
|
|
|
my $basename = basename $fn;
|
|
|
|
|
my $key = "$releasePrefix/" . $basename;
|
|
|
|
|
|
2016-09-26 16:06:51 +00:00
|
|
|
|
unless (defined $bucket->head_key($key)) {
|
|
|
|
|
print STDERR "mirroring $fn to s3://$bucketName/$key...\n";
|
|
|
|
|
$bucket->add_key_filename(
|
|
|
|
|
$key, $fn,
|
|
|
|
|
{ content_type => $fn =~ /.sha256|src-url|binary-cache-url|git-revision/ ? "text/plain" : "application/octet-stream" })
|
2016-09-26 17:50:36 +00:00
|
|
|
|
or die $bucket->err . ": " . $bucket->errstr;
|
2016-09-26 16:06:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-29 09:50:14 +00:00
|
|
|
|
next if $basename =~ /.sha256$/;
|
|
|
|
|
|
|
|
|
|
my $size = stat($fn)->size;
|
|
|
|
|
my $sha256 = Digest::SHA::sha256_hex(read_file($fn));
|
|
|
|
|
$html .= "<tr>";
|
|
|
|
|
$html .= "<td><a href='/$key'>$basename</a></td>";
|
|
|
|
|
$html .= "<td align='right'>$size</td>";
|
|
|
|
|
$html .= "<td><tt>$sha256</tt></td>";
|
|
|
|
|
$html .= "</tr>";
|
2016-09-26 17:50:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-29 09:50:14 +00:00
|
|
|
|
$html .= "</tbody></table></body></html>";
|
|
|
|
|
|
|
|
|
|
$bucket->add_key($releasePrefix, $html,
|
|
|
|
|
{ content_type => "text/html" })
|
2016-09-26 17:50:36 +00:00
|
|
|
|
or die $bucket->err . ": " . $bucket->errstr;
|
2016-09-26 16:06:51 +00:00
|
|
|
|
|
|
|
|
|
File::Path::remove_tree($tmpDir);
|
2016-03-01 19:00:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-24 22:00:11 +00:00
|
|
|
|
# Prevent concurrent writes to the channels directory.
|
|
|
|
|
open(my $lockfile, ">>", "$channelsDir/.htaccess.lock");
|
|
|
|
|
flock($lockfile, LOCK_EX) or die "cannot acquire channels lock\n";
|
|
|
|
|
|
|
|
|
|
# Update the channel.
|
|
|
|
|
my $htaccess = "$channelsDir/.htaccess-$channelName";
|
|
|
|
|
my $target = "https://releases.nixos.org/$releasePrefix";
|
|
|
|
|
write_file($htaccess,
|
|
|
|
|
"Redirect /channels/$channelName $target\n" .
|
|
|
|
|
"Redirect /releases/nixos/channels/$channelName $target\n");
|
|
|
|
|
|
|
|
|
|
my $channelLink = "$channelsDir/$channelName";
|
|
|
|
|
if ((read_file($channelLink, err_mode => 'quiet') // "") ne $target) {
|
|
|
|
|
write_file("$channelLink.tmp", "$target");
|
|
|
|
|
rename("$channelLink.tmp", $channelLink) or die;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
system("cat $channelsDir/.htaccess-nix* > $channelsDir/.htaccess.tmp") == 0 or die;
|
|
|
|
|
rename("$channelsDir/.htaccess.tmp", "$channelsDir/.htaccess") or die;
|
2017-07-21 10:10:58 +00:00
|
|
|
|
|
2019-10-17 17:35:28 +00:00
|
|
|
|
# Update the nixos-* branch in the nixpkgs repo. Also update the
|
|
|
|
|
# nixpkgs-channels repo for compatibility.
|
2017-07-21 10:10:58 +00:00
|
|
|
|
system("git remote update origin >&2") == 0 or die;
|
2019-10-29 11:29:31 +00:00
|
|
|
|
system("git push origin $rev:refs/heads/$channelName >&2") == 0 or die;
|
2017-07-21 10:10:58 +00:00
|
|
|
|
system("git push channels $rev:refs/heads/$channelName >&2") == 0 or die;
|
|
|
|
|
|
2019-01-24 22:00:11 +00:00
|
|
|
|
flock($lockfile, LOCK_UN) or die "cannot release channels lock\n";
|
2019-01-24 22:25:12 +00:00
|
|
|
|
|
|
|
|
|
# Upload to nixos.org.
|
2020-02-17 12:16:22 +00:00
|
|
|
|
system("rsync -avx $channelsDir/ hydra-mirror\@download.nixos.org:/releases/channels/") == 0 or die;
|