1 #! /usr/bin/env nix-shell
2 #! nix-shell -i perl -p perl perlPackages.NetAmazonS3 perlPackages.FileSlurp perlPackages.JSON perlPackages.LWPProtocolHttps nix nix.perl-bindings
4 # This command uploads tarballs to tarballs.nixos.org, the
5 # content-addressed cache used by fetchurl as a fallback for when
6 # upstream tarballs disappear or change. Usage:
8 # 1) To upload one or more files:
10 # $ copy-tarballs.pl --file /path/to/tarball.tar.gz
12 # 2) To upload all files obtained via calls to fetchurl in a Nix derivation:
14 # $ copy-tarballs.pl --expr '(import <nixpkgs> {}).hello'
25 isValidPath
("/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-foo"); # FIXME: forces Nix::Store initialisation
28 die "Syntax: $0 [--dry-run] [--exclude REGEXP] [--expr EXPR | --file FILES...]\n";
37 my $flag = shift @ARGV;
39 if ($flag eq "--expr") {
40 $expr = shift @ARGV or die "--expr requires an argument";
41 } elsif ($flag eq "--file") {
44 } elsif ($flag eq "--dry-run") {
46 } elsif ($flag eq "--exclude") {
47 $exclude = shift @ARGV or die "--exclude requires an argument";
55 if (not defined $ENV{DEBUG
}) {
57 my $aws_access_key_id = $ENV{'AWS_ACCESS_KEY_ID'} or die "AWS_ACCESS_KEY_ID not set\n";
58 my $aws_secret_access_key = $ENV{'AWS_SECRET_ACCESS_KEY'} or die "AWS_SECRET_ACCESS_KEY not set\n";
60 my $s3 = Net
::Amazon
::S3
->new(
61 { aws_access_key_id
=> $aws_access_key_id,
62 aws_secret_access_key
=> $aws_secret_access_key,
64 host
=> "s3-eu-west-1.amazonaws.com",
67 $bucket = $s3->bucket("nixpkgs-tarballs") or die;
71 my $cacheFile = ($ENV{"HOME"} or die "\$HOME is not set") . "/.cache/nix/copy-tarballs";
73 $cache{$_} = 1 foreach read_file
($cacheFile, err_mode
=> 'quiet', chomp => 1);
77 File
::Path
::mkpath
(dirname
($cacheFile), 0, 0755);
78 write_file
($cacheFile, map { "$_\n" } keys %cache) if $doWrite;
82 my ($algo, $hash) = @_;
83 my $key = "$algo/$hash";
84 return 1 if defined $cache{$key};
85 my $res = defined $bucket->get_key($key);
86 $cache{$key} = 1 if $res;
93 my $md5_16 = hashFile
("md5", 0, $fn) or die;
94 my $sha1_16 = hashFile
("sha1", 0, $fn) or die;
95 my $sha256_32 = hashFile
("sha256", 1, $fn) or die;
96 my $sha256_16 = hashFile
("sha256", 0, $fn) or die;
97 my $sha512_32 = hashFile
("sha512", 1, $fn) or die;
98 my $sha512_16 = hashFile
("sha512", 0, $fn) or die;
100 my $mainKey = "sha512/$sha512_16";
102 # Create redirects from the other hash types.
104 my ($name, $dest) = @_;
105 #print STDERR "linking $name to $dest...\n";
106 $bucket->add_key($name, "", {
107 'x-amz-website-redirect-location' => "/" . $dest,
108 'x-amz-acl' => "public-read"
110 or die "failed to create redirect from $name to $dest\n";
113 redirect
"md5/$md5_16", $mainKey;
114 redirect
"sha1/$sha1_16", $mainKey;
115 redirect
"sha256/$sha256_32", $mainKey;
116 redirect
"sha256/$sha256_16", $mainKey;
117 redirect
"sha512/$sha512_32", $mainKey;
119 # Upload the file as sha512/<hash-in-base-16>.
120 print STDERR
"uploading $fn to $mainKey...\n";
121 $bucket->add_key_filename($mainKey, $fn, {
122 'x-amz-meta-original-name' => $name,
123 'x-amz-acl' => "public-read"
125 or die "failed to upload $fn to $mainKey\n";
126 $cache{$mainKey} = 1;
129 if (scalar @fileNames) {
131 foreach my $fn (@fileNames) {
133 if (alreadyMirrored
("sha512", hashFile
("sha512", 0, $fn))) {
134 print STDERR
"$fn is already mirrored\n";
136 uploadFile
($fn, basename
$fn);
147 elsif (defined $expr) {
149 # Evaluate find-tarballs.nix.
150 my $pid = open(JSON
, "-|", "nix-instantiate", "--eval", "--json", "--strict",
151 "<nixpkgs/maintainers/scripts/find-tarballs.nix>",
152 "--arg", "expr", $expr);
155 die "$0: evaluation failed\n" if $?
;
158 my $fetches = decode_json
($stdout);
160 print STDERR
"evaluation returned ", scalar(@
{$fetches}), " tarballs\n";
162 # Check every fetchurl call discovered by find-tarballs.nix.
165 foreach my $fetch (sort { $a->{urls
}->[0] cmp $b->{urls
}->[0] } @
{$fetches}) {
166 my $urls = $fetch->{urls
};
167 my $algo = $fetch->{type
};
168 my $hash = $fetch->{hash
};
169 my $name = $fetch->{name
};
170 my $isPatch = $fetch->{isPatch
};
173 print STDERR
"skipping $urls->[0] (support for patches is missing)\n";
177 if ($hash =~ /^([a-z0-9]+)-([A-Za-z0-9+\/=]+)$/) {
179 $hash = `nix hash to-base16 $hash` or die;
183 next unless $algo =~ /^[a-z0-9]+$/;
185 # Convert non-SRI base-64 to base-16.
186 if ($hash =~ /^[A-Za-z0-9+\/=]+$/) {
187 $hash = `nix hash to-base16 --type '$algo' $hash` or die;
191 my $storePath = makeFixedOutputPath
(0, $algo, $hash, $name);
193 for my $url (@
$urls) {
194 if (defined $ENV{DEBUG
}) {
195 print "$url $algo $hash\n";
199 if ($url !~ /^http:/ && $url !~ /^https:/ && $url !~ /^ftp:/ && $url !~ /^mirror:/) {
200 print STDERR
"skipping $url (unsupported scheme)\n";
204 next if defined $exclude && $url =~ /$exclude/;
206 if (alreadyMirrored
($algo, $hash)) {
211 print STDERR
"mirroring $url ($storePath, $algo, $hash)...\n";
218 # Substitute the output.
219 if (!isValidPath
($storePath)) {
220 system("nix-store", "-r", $storePath);
223 # Otherwise download the file using nix-prefetch-url.
224 if (!isValidPath
($storePath)) {
226 $ENV{PRINT_PATH
} = 1;
228 my $pid = open($fh, "-|", "nix-prefetch-url", "--type", $algo, $url, $hash) or die;
229 waitpid($pid, 0) or die;
231 print STDERR
"failed to fetch $url: $?\n";
234 <$fh>; my $storePath2 = <$fh>; chomp $storePath2;
235 if ($storePath ne $storePath2) {
236 warn "strange: $storePath != $storePath2\n";
241 uploadFile
($storePath, $url);
247 print STDERR
"mirrored $mirrored files, already have $have files\n";