about summary refs log blame commit diff
path: root/scripts/nix-channel.in
blob: 0b499c3f7927b0c866095dfb5890ab04b8f007e9 (plain) (tree)
1
2
3
4
5
6
7
8
9
                        
 
         
           
                   
                          
                
                  
                           
 

                                  

                        
                                            
 
 
                                      
                                                          
                                                  
                                                               
 
                                                         
                                                 
                                         
                                      
 
                                              
                                                                                 
                                                                            
                                  
 
             

 
                             

                                  
                                                                                 

                        
                         



                                                   




                   
                              
                   
                                                                                 

                                                  




                   
                 
                
                          

                                                                                                   
                 
                            



                  
                   
                   
                    
                 

                                                                  
                            
                  

                                                                               
                                                     


 
                                                              

            
                          
 
                 
 


                                       
                                                                                   
 
                                   
                                      
 





                                                                                                                                           
                                                          
                   
 





                                                                     













































                                                                                                                        
                                                                                                                            
     
 





                                                                     
 

                                                                    
                                                                                            

                                             
                                                                                                 
 
 
 
                                                   

 



                          
                                                                                                      







                                   


             
                             
                                                                              
                                   



                           
                                                                            
                     

                                             



             
                                
                      

             
 













                                                                                                       
                              
                                      
     
 



                                                          
 
          
                                                              

     
#! @perl@ -w @perlFlags@

use utf8;
use strict;
use File::Basename;
use File::Path qw(mkpath);
use Nix::Config;
use Nix::Manifest;
use File::Temp qw(tempdir);

binmode STDERR, ":encoding(utf8)";

Nix::Config::readConfig;

my $manifestDir = $Nix::Config::manifestDir;


# Turn on caching in nix-prefetch-url.
my $channelCache = "$Nix::Config::stateDir/channel-cache";
mkdir $channelCache, 0755 unless -e $channelCache;
$ENV{'NIX_DOWNLOAD_CACHE'} = $channelCache if -W $channelCache;

# Figure out the name of the `.nix-channels' file to use.
my $home = $ENV{"HOME"} or die '$HOME not set\n';
my $channelsList = "$home/.nix-channels";
my $nixDefExpr = "$home/.nix-defexpr";

# Figure out the name of the channels profile.
my $userName = getpwuid($<) || $ENV{"USER"} or die "cannot figure out user name";
my $profile = "$Nix::Config::stateDir/profiles/per-user/$userName/channels";
mkpath(dirname $profile, 0, 0755);

my %channels;


# Reads the list of channels.
sub readChannels {
    return if (!-f $channelsList);
    open CHANNELS, "<$channelsList" or die "cannot open ‘$channelsList’: $!";
    while (<CHANNELS>) {
        chomp;
        next if /^\s*\#/;
        my ($url, $name) = split ' ', $_;
        $url =~ s/\/*$//; # remove trailing slashes
        $name = basename $url unless defined $name;
        $channels{$name} = $url;
    }
    close CHANNELS;
}


# Writes the list of channels.
sub writeChannels {
    open CHANNELS, ">$channelsList" or die "cannot open ‘$channelsList’: $!";
    foreach my $name (keys %channels) {
        print CHANNELS "$channels{$name} $name\n";
    }
    close CHANNELS;
}


# Adds a channel.
sub addChannel {
    my ($url, $name) = @_;
    die "invalid channel URL ‘$url’" unless $url =~ /^(file|http|https):\/\//;
    die "invalid channel identifier ‘$name’" unless $name =~ /^[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*$/;
    readChannels;
    $channels{$name} = $url;
    writeChannels;
}


# Remove a channel.
sub removeChannel {
    my ($name) = @_;
    readChannels;
    my $url = $channels{$name};
    deleteOldManifests($url . "/MANIFEST", undef) if defined $url;
    delete $channels{$name};
    writeChannels;

    system("$Nix::Config::binDir/nix-env --profile '$profile' -e '$name'") == 0
        or die "cannot remove channel ‘$name’\n";
}


# Fetch Nix expressions and pull manifests from the subscribed
# channels.
sub update {
    my @channelNames = @_;

    readChannels;

    # Download each channel.
    my $exprs = "";
    foreach my $name (keys %channels) {
        next if scalar @channelNames > 0 && ! grep { $_ eq $name } @{channelNames};

        my $url = $channels{$name};
        my $origUrl = "$url/MANIFEST";

        # We want to download the url to a file to see if it's a tarball while also checking if we
        # got redirected in the process, so that we can grab the various parts of a nix channel
        # definition from a consistent location if the redirect changes mid-download.
        my $tmpdir = tempdir( CLEANUP => 1 );
        my $filename;
        ($url, $filename) = `cd $tmpdir && $Nix::Config::curl --silent --write-out '%{url_effective}\n%{filename_effective}' -L '$url' -O`;
        die "$0: unable to check ‘$url’\n" if $? != 0;
        chomp $url;

        # If the URL contains a version number, append it to the name
        # attribute (so that "nix-env -q" on the channels profile
        # shows something useful).
        my $cname = $name;
        $cname .= $1 if basename($url) =~ /(-\d.*)$/;

        my $path;
        my $ret = -1;
        if (-e "$tmpdir/$filename") {
            # Get our temporary download into the store
            (my $hash, $path) = `PRINT_PATH=1 QUIET=1 $Nix::Config::binDir/nix-prefetch-url 'file://$tmpdir/$filename'`;
            chomp $path;

            # Try unpacking the expressions to see if they'll be valid for us to process later.
            # Like anything in nix, this will cache the result so we don't do it again outside of the loop below
            $ret = system("$Nix::Config::binDir/nix-build --no-out-link -E 'import <nix/unpack-channel.nix> " .
                          "{ name = \"$cname\"; channelName = \"$name\"; src = builtins.storePath \"$path\"; }'");
        }

        # The URL doesn't unpack directly, so let's try treating it like a full channel folder with files in it
        my $extraAttrs = "";
        if ($ret != 0) {
            # Check if the channel advertises a binary cache.
            my $binaryCacheURL = `$Nix::Config::curl --silent '$url'/binary-cache-url`;
            my $getManifest = ($Nix::Config::config{"force-manifest"} // "false") eq "true";
            if ($? == 0 && $binaryCacheURL ne "") {
                $extraAttrs .= "binaryCacheURL = \"$binaryCacheURL\"; ";
                deleteOldManifests($origUrl, undef);
            } else {
                $getManifest = 1;
            }

            if ($getManifest) {
                # No binary cache, so pull the channel manifest.
                mkdir $manifestDir, 0755 unless -e $manifestDir;
                die "$0: you do not have write permission to ‘$manifestDir’!\n" unless -W $manifestDir;
                $ENV{'NIX_ORIG_URL'} = $origUrl;
                system("$Nix::Config::binDir/nix-pull", "--skip-wrong-store", "$url/MANIFEST") == 0
                    or die "cannot pull manifest from ‘$url’\n";
            }

            # Download the channel tarball.
            my $fullURL = "$url/nixexprs.tar.xz";
            system("$Nix::Config::curl --fail --silent --head '$fullURL' > /dev/null") == 0 or
                $fullURL = "$url/nixexprs.tar.bz2";
            print STDERR "downloading Nix expressions from ‘$fullURL’...\n";
            (my $hash, $path) = `PRINT_PATH=1 QUIET=1 $Nix::Config::binDir/nix-prefetch-url '$fullURL'`;
            die "cannot fetch ‘$fullURL’\n" if $? != 0;
            chomp $path;
        }

        # Regardless of where it came from, add the expression representing this channel to accumulated expression
        $exprs .= "'f: f { name = \"$cname\"; channelName = \"$name\"; src = builtins.storePath \"$path\"; $extraAttrs }' ";
    }

    # Unpack the channel tarballs into the Nix store and install them
    # into the channels profile.
    print STDERR "unpacking channels...\n";
    system("$Nix::Config::binDir/nix-env --profile '$profile' " .
           "-f '<nix/unpack-channel.nix>' -i -E $exprs --quiet") == 0
           or die "cannot unpack the channels";

    # Make the channels appear in nix-env.
    unlink $nixDefExpr if -l $nixDefExpr; # old-skool ~/.nix-defexpr
    mkdir $nixDefExpr or die "cannot create directory ‘$nixDefExpr’" if !-e $nixDefExpr;
    my $channelLink = "$nixDefExpr/channels";
    unlink $channelLink; # !!! not atomic
    symlink($profile, $channelLink) or die "cannot symlink ‘$channelLink’ to ‘$profile’";
}


die "$0: argument expected\n" if scalar @ARGV == 0;


while (scalar @ARGV) {
    my $arg = shift @ARGV;

    if ($arg eq "--add") {
        die "$0: ‘--add’ requires one or two arguments\n" if scalar @ARGV < 1 || scalar @ARGV > 2;
        my $url = shift @ARGV;
        my $name = shift @ARGV;
        unless (defined $name) {
            $name = basename $url;
            $name =~ s/-unstable//;
            $name =~ s/-stable//;
        }
        addChannel($url, $name);
        last;
    }

    if ($arg eq "--remove") {
        die "$0: ‘--remove’ requires one argument\n" if scalar @ARGV != 1;
        removeChannel(shift @ARGV);
        last;
    }

    if ($arg eq "--list") {
        die "$0: ‘--list’ requires one argument\n" if scalar @ARGV != 0;
        readChannels;
        foreach my $name (keys %channels) {
            print "$name $channels{$name}\n";
        }
        last;
    }

    elsif ($arg eq "--update") {
        update(@ARGV);
        last;
    }

    elsif ($arg eq "--rollback") {
        die "$0: ‘--rollback’ has at most one argument\n" if scalar @ARGV > 1;
        my $generation = shift @ARGV;
        my @args = ("$Nix::Config::binDir/nix-env", "--profile", $profile);
        if (defined $generation) {
            die "invalid channel generation number ‘$generation’" unless $generation =~ /^[0-9]+$/;
            push @args, "--switch-generation", $generation;
        } else {
            push @args, "--rollback";
        }
        system(@args) == 0 or exit 1;
        last;
    }

    elsif ($arg eq "--help") {
        exec "man nix-channel" or die;
    }

    elsif ($arg eq "--version") {
        print "nix-channel (Nix) $Nix::Config::version\n";
        exit 0;
    }

    else {
        die "unknown argument ‘$arg’; try ‘--help’\n";
    }
}