#!/bin/sh
#
# Manage openpkg package distribution - see Usage() for details.
#
# $Id: openpkg-mgr,v 1.19 2003/02/25 21:41:56 mandrews Exp $

set -e

#-------
# Configuration settings (!!!allow setting from config file?)
autofs=/netgenics
opdir=/opt/openpkg
rpmurl=http://bluefish.cle.lionbioscience.com/local/openpkg-rpm
user=openpkg
group=openpkg

configdir=$opdir/.opmgr
mybindir=$opdir/RPM/PKG

srcroot=$autofs/openpkg-src
cache=$opdir/RPM/SRPM

platform=`/etc/cfengine/scripts/platform`
binroot=$autofs/openpkg-rpm/$platform
binurl=$rpmurl/$platform

# End configuration settings
#-------

# Process command-line

mode=$1
shift

# If test mode pass command to cat (to display) instead of sh (to run).
case $mode in
test-*) shell=cat ;;
*) shell=sh ;;
esac

force=no
tmplink=yes
while true
do
    case $1 in
    --force|-f) shift; force=yes ;;
    --no-templink|-L) shift; tmplink=no ;;
    --repository|-r) shift; repository=$1; shift ;;
    --srpms|-s) shift; cache=$1; shift ;;
    --) shift; break ;;
    *) break ;;
    esac
done

Usage()
{
    cat <<EOF
Usage: openpkg-mgr mode [options] [arg ..]

Modes: 
    bootstrap	- Initialize binary repository for new hardware platform
    init	- Initialize new host
    trial	- Grab source rpms from the net and try building them
    release	- Release source rpms to local source repository
    build	- Update this platforms binary platform to match source
    update	- Update installed rpms to latest from binary repository

Options:
    -f/--force		- Make a new release even if there are no changes
    -L/--no-templink	- Don't create binary rpm links in RPM/PKG (hack)
    -r/--repository url	- URL for network source rpms
    -s/--srpms dir	- directory for scratch source rpms

openpkg-mgr manages the download, compiling and installation of openpkg
packages (see http://www.openpkg.org for details on the openpkg
project).
EOF
}

# Link all available binary RPM's to local directory - so build will
# use them as required.
LinkMyBin()
{
    test "$tmplink" = "no" && return 0
    for path in $bindir/*.rpm
    do
	# Skip if central rpm does not exist (not pattern match?)
	test -f $path || continue

	# Link to central rpm if local one does not already exist
	rpm=`basename $path`
	test -f $mybindir/$rpm || ln -s $bindir/$rpm $mybindir/
    done
    return 0
}

# Remove the linked binary RPMs from mybin
UnlinkMyBin()
{
    test "$tmplink" = "no" && return 0
    for rpm in $mybindir/*.rpm
    do
	# Remove local rpm if it is a link
	test -h $rpm && rm $rpm
    done
    return 0
}

# Get current release number for release tree
ReleaseCurrent()
{
    if [ -f "$1/relnum" ]
    then
	cat "$1/relnum"
    else
	echo 0
    fi
}

# Allocate new release number for release tree
ReleaseAllocate()
{
    root="$1"
    mkdir -p $root
    relnum=`ReleaseCurrent $root`
    while true
    do
	relnum=`expr $relnum + 1`
	mkdir "$root/$relnum" 2>/dev/null && break
    done
    echo $relnum
}

# Commit prior allocated release
ReleaseCommit()
{
    root="$1"
    relnum="$2"
    echo $relnum >$root/newnum
    mv $root/newnum $root/relnum
    rm -f $root/current
    ln -s $relnum $root/current
}


InstallRpms()
{
    from=$1
    root=$2
    current=$3

    # Set up new repository
    relnum=`ReleaseAllocate $root`
    target=$root/$relnum

    echo "Creating new release: $target"

    # Copy new rpms to repository
    cp $from/*.rpm $target/

    # Link in specified subdirectories to repository
    if [ -f $from/links ]
    then
	cat $from/links | \
	{
	    while read name src
	    do
		ln -s $src $target/$name
	    done
	}
    fi

    # Link rpms that haven't been updated
    # !!! include option to exclude obsolete RPMs?
    for rpm in $current/*.rpm
    do
	test -f $rpm || continue
	# Get package name from RPM
	name=`rpm -q --queryformat="%{NAME}" -p $rpm`

	if [ ! -f $target/$name-[0-9]*.rpm ]
	then
	    # No version of that package in repository - link this one in
	    ln $rpm $target/
	fi
    done

    # Make index
    openpkg index -i -p $platform -o $target/00INDEX.rdf $target

    # Finalize
    ReleaseCommit $root $relnum

    # Remove local copy
    rm -f $from/*.rpm $from/links
}

# Initialize a new host or even a new platform
Bootstrap() {
    scope=$1
    bootdir=$srcroot/bootstrap
    bootscript=$bootdir/$platform.sh

    me=`id | sed 's/[^(]*(//;s/).*//'`
    case $me in
    root)
	echo "don't bootstrap as root!"
	exit 1
	;;
    esac

    tmpdir=/tmp/boot$$
    mkdir $tmpdir
    cd $tmpdir

    # Install base openpkg
    if [ "$scope" = "platform" -a ! -f $bootscript ]
    then
	# No bootstrap script for this platform yet - really bootstrap from
	# source.

	sh $bootdir/openpkg-*.src.sh \
	    --prefix=$opdir \
	    --user=$user \
	    --group=$group \
	    
	mv openpkg-*.sh $bootscript
    fi
    sh $bootscript

    cd /
    rm -rf $tmpdir

    # Load (new) openpkg environment
    eval `$opdir/etc/rc --eval all env`

    # Install openpkg-tool
    if [ "$scope" = "platform" ]
    then
	rpm --rebuild $srcdir/openpkg-tool-*.src.rpm
	rpm -U $mybindir/openpkg-tool-*.rpm
    else
	rpm -U $bindir/openpkg-tool-*.rpm
    fi

    if [ "$scope" = "platform" ]
    then
	# Link in alternate cc if one exists (required on solaris at least) -
	# will go away once gcc is installed.
	# Supposedly we could do without this link by using
	# -Dwith_cc=/opt/SUNWspro/bin/cc when we build gcc, but that then make
	# does not find cc (have to pass it explicitly there too?)
	for cc in /opt/SUNWspro/bin/cc
	do
	    if [ -f $cc ]
	    then
		ln -s $cc $opdir/bin/cc
		break
	    fi
	done
    fi
}

# Perl script to filter "openpkg build" output for "trial" mode.
# Replaces single download and build lines with two steps that will save
# the source RPM locally.

trial_filter='
    use strict;
    my (@srcs) = @ARGV;
    my $cache = $srcs[0];
    while (<STDIN>) {
	if (my ($rpm, $opts, $url, $end) =
		( m{^([^#]\S*/rpm)(\s.*--rebuild )(\S*)(.*)} )) {
	    # rpm --rebuild command.

	    # Search local source directories for RPM
	    my $basename = (split("/", $url))[-1];
	    my $src;
	    foreach my $dir (@srcs) {
		my $rpm = "$dir/$basename";
		if (-f $rpm) {
		    $src = $rpm;
		    last;
		}
	    }
	    if (!defined $src) {
		# RPM not found - download it to cache
		$src = "$cache/$basename";
		print "curl -f -s -S $url -o $src || exit \$?\n";
	    }

	    print "$rpm$opts$src$end\n";
	} else {
	    print $_;
	}
    }
    '

# Perl script to filter "openpkg build" output for "update" mode.
# Ignores all "rpm --rebuild" commands and replaces location of all RPM's
# in "rpm -U" commands with the specified repository directory.

update_filter='
    use strict;
    my $rpmdir = $ARGV[0];
    while (<STDIN>) {
	if ( m{^[^#]\S*/rpm\s.*--rebuild } ) {
	    # rpm --rebuild command - ignore it.
	    print "# ", $_;
	} elsif (my ($rpm, $opts, $rpmpath, $end) =
		( m{^([^#]\S*/rpm)\s+(.*-U\S*)\s+(/\S*)(.*)} )) {
	    # rpm -U command - substitute source directory
	    my $basename = (split("/", $rpmpath))[-1];
	    print "$rpm $opts $rpmdir/$basename$end\n";
	} else {
	    print $_;
	}
    }
    '

#----------
# Main


bindir=$binroot/`ReleaseCurrent $binroot`
srcdir=$srcroot/`ReleaseCurrent $srcroot`


case $mode in
trial|test-trial)
    # Trying building rpm's - save source rpm's in cache

    test -d $cache || mkdir -p $cache
    LinkMyBin

    opts="-U -e"
    if [ ! -z "$repository" ]
    then
	opts="$opts -r $repository -f $repository/00INDEX.rdf.bz2"
    fi

    for pkg in $*
    do
	openpkg build -P sudo $opts $pkg \
		| perl -e "$trial_filter" -- $cache $srcdir \
		| $shell
    done
    UnlinkMyBin
    ;;

release)
    # Copy source RPMs from trial build to source repository

    InstallRpms $cache $srcroot $srcdir
    ;;

build|test-build)
    # Update this platform's binary repository from source

    LinkMyBin

    cat $configdir/build | {
	while read pkg opts
	do
	    openpkg build -r $srcdir -P sudo -U -e $opts $pkg \
		    | $shell
	done
    }

    UnlinkMyBin

    test $mode = "test-build" && exit

    # !!! Where do I store the netlinked rpms? - how do I make sure I get
    # dependencies for local rpms  install locall too? still need to work
    # out some sticky issues...
#    cd $
#    for f in $mybindir/*.rpm
#    do
#	rpm --makeproxy --prefix=$netopdir $rpm
#    done

    # Check for rpm in local rpm directory. Use loop to test if any file
    # matches pattern (looks silly - but I think this is the best way to do
    # this in shell).
    for rpm in $mybindir/*.rpm
    do
	# Update binary repository if new rpm exists
	if [ "$force" = "yes" -o -f $rpm ]
	then
	    # Include link to source repository
	    echo "src $srcdir" >$mybindir/links

	    InstallRpms $mybindir $binroot $bindir
	fi
	break
    done
    ;;

update|test-update)
    # Update to latest versions of desired RPM's (using binary repository).
    # !!! This mode needs to be run as root!

    if [ ! -x $opdir/bin/rpm ]
    then
	Bootstrap host
    fi

    if [ ! -d $bindir ]
    then
	# Can't find binary repository - try via curl.
	relnum=`curl -f -s -S $binurl/relnum`
	bindir=$binurl/$relnum
    fi

    openpkg build -U -e -p $platform -r $bindir "$@" \
	    | perl -e "$update_filter" -- $bindir | $shell
    ;;

init)
    Bootstrap host
    ;;
bootstrap|init)
    Bootstrap platform

    ;;

*)
    echo "unknown mode: $mode" 1>&2
    Usage
    exit 1
    ;;
esac


