repository [download]


#!/usr/bin/perl -w
# $Revision: 1.16 $
# $Date: 2006-04-15 17:58:49 $
# Luis Mondesi < lemsx1@gmail.com >
#
# DESCRIPTION: easy repository builder
# USAGE: repository <add|remove|update>
# LICENSE: GPL

=pod

=head1 NAME

repository - repository builder script for Debian and/or Fedora

=head1 DESCRIPTION 

    This script can be used to build a repository for Linux distros quickly

    To make configuration simpler, create a file called /etc/repositoryrc or ~/.repositoryrc
    and put the following values:
        DISTRIBUTION=my_debian_distro
        WORKDIR=/path/to/repository

    Values passed from the command line will override these.

=cut

use strict;
$|++;

my $revision = '$Revision: 1.16 $';    # version
$revision =~ s/(\\|Revision:|\s|\$)//g;

# standard Perl modules
use Getopt::Long;
Getopt::Long::Configure('bundling');
use POSIX;                    # cwd() ... man POSIX
use File::Spec::Functions;    # abs2rel() and other dir/filename specific

# Args:
my $PVERSION = 0;
my $HELP     = 0;
my $USAGE    = 0;
my $DEBUG    = 0;
my $FUNCTION = undef;                                    # add remove update
my $WORKDIR  = _get_option("WORKDIR", "/home/Shared");
my $DISTRO   = _get_option("DISTRIBUTION", "debian");

=pod

=head1 SYNOPSIS

B<repository>  [-v,--version]
                [-D,--debug] 
                [-h,--help]
                [-U,--usage]
                [-d,--distribution DISTRIB]
                [-w,--work-dir DIR]
                <add|remove|update> [file1 file2 ... fileN]

=head1 OPTIONS

=over 8

=item -v,--version

Prints version and exits

=item -D,--debug

Enables debug mode

=item -d,--distribution DISTRIB

Use distribution DISTRIB instead of default (debian)

=item -h,--help

Prints this help and exits

=item -U,--usage

Prints usage information and exits

=item -w,--work-dir DIR

Use directory DIR as the work area. This directory contains a directory named the same way as the distribution. See --distribution

=item add

Adds all files found in the directories incoming/$codename and incoming/*

=item remove [file]

Removes old packages from the repository (reprepro deleteunreferenced). If file name is passed, it will remove only that package from all the $codenames found in conf/distributions

=item update

Updates all packages in the repository using the defined update method. For reprepro this means having a conf/update file

=back

=cut

# get options
GetOptions(

    # flags
    'v|version' => \$PVERSION,
    'h|help'    => \$HELP,
    'D|debug'   => \$DEBUG,
    'U|usage'   => \$USAGE,

    # strings
    'd|distribution=s' => \$DISTRO,
    'w|work-dir=s'     => \$WORKDIR,
  )
  and $FUNCTION = shift;

if ($HELP)
{
    use Pod::Text;
    my $parser = Pod::Text->new(sentence => 0, width => 78);
    $parser->parse_from_file($0, \*STDOUT);
    exit 0;
}

if (   $USAGE
    or not defined($FUNCTION)
    or $FUNCTION =~ /^\s*$/
    or !-d $WORKDIR
    or !-d File::Spec->catdir($WORKDIR, $DISTRO))
{
    use Pod::Usage;
    pod2usage(1);
    exit 0;    # never reaches here
}

if ($PVERSION) { print STDOUT ($revision, "\n"); exit 0; }

my $distro = $DISTRO;
if ($distro =~ /^\s*$/ and -x "/usr/bin/lsb_release")
{
    $distro = qx#/usr/bin/lsb_release -i#;
    $distro =~ s#Distributor\s+ID:\s+##i;
}

chomp($distro);
$distro = lc($distro);

die("Please make sure lsb_release is installed and in our PATH\n")
  if ($distro =~ /^\s*$/);

my $workdir = File::Spec->catdir($WORKDIR, $distro);
chdir($workdir) or die("Could not change into directory $workdir. $!\n");

if ($distro =~ /ubuntu|debian/i)
{
    my $cmd = "reprepro";    # TODO allow others to be configured from CLI
    my $cmd_args = " --delete --ignore=forbiddenchar ";

    # sanity checks
    die("No ${workdir}/conf/distributions file found. Reprepro is not installed correctly\n"
       )
      if (not -f "conf/distributions");

    # We read conf/distributions from reprepro to support .deb's for multiple distributions:
    # sarge, woody, etch, breezy, etc...
    # Then the directories in incoming would be split like:
    # incoming/$codename
    # Where codename is a name like the ones from above
    open(FILE, "<", "conf/distributions")
      or die("Could not read ${workdir}/conf/distributions file. $!\n");

    my @distributions = ();
    while (<FILE>)
    {
        if ($_ =~ /Codename:[[:space:]]*([[:alnum:]]+)/i)
        {
            push(@distributions, $1);
        }
    }
    close(FILE);

    print STDERR ("Distributions in conf/distributions: ",
                  join(" ", @distributions), "\n")
      if ($DEBUG);

    if ($FUNCTION eq 'add')
    {

        # adding all files in "incoming"
        # .changes:
        print STDERR ("Adding .changes files\n") if ($DEBUG);
        foreach my $file (glob("incoming/*.changes"))
        {
            my $distrib = _get_distribution($file);
            print STDERR ("Adding $file to distrib $distrib\n")
              if ($DEBUG);
            system("$cmd $cmd_args -b . include $distrib $file");

            #die("Error while including $file\n") if ($? != 0);
        }

        # .dsc:
        print STDERR ("Adding sources files\n") if ($DEBUG);
        foreach my $file (glob("incoming/*.dsc"))
        {
            my $distrib = _get_distribution($file);
            print STDERR ("Adding $file to distrib $distrib\n")
              if ($DEBUG);
            system("$cmd $cmd_args -b . includedsc $distrib $file");

            #die("Error while including sources for $file\n") if ($? != 0);
        }

        # .deb for e/a distrib:
        print STDERR ("Adding .deb files\n") if ($DEBUG);
        foreach my $distrib (@distributions)
        {
            foreach my $file (glob("incoming/$distrib/*.deb"))
            {
                print STDERR ("Adding $file to distrib $distrib\n")
                  if ($DEBUG);
                system("$cmd $cmd_args -b . includedeb $distrib $file");

                #die("Error while including sources for $file\n") if ($? != 0);
            }
        }
    }
    elsif ($FUNCTION eq 'remove')
    {
        if ((@ARGV + 0) > 0)
        {
            for my $file (@ARGV)
            {
                for my $d (@distributions)
                {

                    # TODO make this silently...
                    system("$cmd $cmd_args remove $d $file ");
                }
            }
        }
        else
        {
            system("$cmd $cmd_args deleteunreferenced");
        }
    }

    system("reprepro --delete createsymlinks")
      if ($cmd eq "reprepro");

}
else
{
    print STDERR ("Distribution $distro not supported\n");
    print STDERR ("is lsb_release installed? try: lsb_release -i\n");
}

# a very quick and dirty way to read a configuration file, search for a given string $o
# and return its value or a default string given by $d if no such string found in
# rc file (or .ini file)
# File is assumed to define variables like:
# OPT=value
sub _get_option
{
    my $o = shift;
    my $d = shift;

    # sanity checks:
    return $d if (not defined $o);
    my @files =
      ("/etc/repositoryrc", File::Spec->catfile($ENV{'HOME'}, ".repositoryrc"));
    foreach my $_file (@files)
    {
        next if (!-r $_file);
        open(FILE, "<$_file") or return $d;
        while (<FILE>)
        {
            if ($_ =~ m#$o\s*=\s*(.+)#)
            {
                $d = $1;    # we need to read ~/.distributionrc last
            }
        }
        close(FILE);
    }
    $d =~ s/\s+$//;         # remove trailing spaces and ugly chars
    return $d;
}

# returns the distribution of a .changes file
sub _get_distribution
{
    my $file = shift;
    die("File $file not plain text\n") if (not -f $file);
    my $distrib = 'unstable';
    open(FILE, "<$file")
      or die("Could not open file $file for reading. $!\n");
    while (<FILE>)
    {
        if ($_ =~ /Distribution:[[:space:]]*([[:alnum:]]+)/i)
        {
            $distrib = $1;
            chomp($distrib);
            return $distrib;
            last;    # never reached
        }
    }
    return $distrib;    # should never reach here
}

=pod

=head1 AUTHORS

Luis Mondesi <lemsx1@gmail.com>

=cut