This is what I've come up with.  It required a small patch to my
Config.pm file (hack!), but it works perfectly in all of my tests on
Linux and Windows XP.  Must either be admin/root or have write privs
for the dirs.  I haven't tested Mac OS X yet, but I expect that it'll
be similar or same as Linux.

My goals:
- use only the modules that are installed and managed by my program
only, ignoring all system installed modules
- prefer modules that were installed via PAR repository over ones
packaged in PAR-packed binary
- fetch from PAR repository only if unavailable within
PAR-repo-installed module area and PAR-packed binary area
- do not contact PAR repository at all unless a module is needed to be installed
- do not die if computer is offline or repo is unavailable
- allow continued function even if computer goes online/offline during
run of app
- allow for simple module upgrade on-demand (e.g.: receive message
from remote server telling local client to upgrade, etc)
- do not modify/upgrade/remove/use any locally installed system-wide modules
- allow other apps installed that use the same base directory to use
modules installed via this code
- loader.pl must be cross-platform code with no "special" versions for
a different OS
- loader.exe/loader.bin contain as few pre-packaged modules as
reasonably possible

Sorry about any wrapped lines.  If it looks funny, it probably belongs
on the end of the previous line.

If anyone has any comments/suggestions/love/hate or better methods,
I'd love to hear about it.



############################## file: loader.pl
# (packaged via "pp -vvv -c -o loader.exe loader.pl" for Windows
# and "pp -vvv -c -o loader.bin loader.pl" for Linux)
BEGIN
{
  if ($^O =~ m/^mswin/i)
  {
    $ENV{LB_BASE_DIR} = 'C:\\Program Files\\LeafBridge';
    $ENV{LB_PERL_DIR} = $ENV{LB_BASE_DIR}.'\\pm';
    @{$ENV{LB_PERL_INC}} = ($ENV{LB_PERL_DIR}.'\\site\\lib');
  }
  else
  {
    $ENV{LB_BASE_DIR} = '/opt/LeafBridge';
    $ENV{LB_PERL_DIR} = $ENV{LB_BASE_DIR}.'/pm';
    @{$ENV{LB_PERL_INC}} =
($ENV{LB_PERL_DIR}.'/local/lib/perl/5.10.0',
$ENV{LB_PERL_DIR}.'/local/share/perl/5.10.0');
  }
  $ENV{LB_REPO_URI} = "http://par.leafbridge.net/";;
  $ENV{RES_NAMESERVERS} = "208.67.222.222 208.67.220.220"; # OpenDNS,
to allow DNS even if local network DNS has failed
  $ENV{RES_SEARCHLIST} = "leafbridge.net";
};

my $LB_BASE_DIR = $ENV{LB_BASE_DIR};
mkdir($LB_BASE_DIR);
my $LB_PERL_DIR = $ENV{LB_PERL_DIR};
mkdir($LB_PERL_DIR);

use PAR::Repository::Client;
use PAR::Repository::Client::Util; # force into pack
use PAR::Repository::Client::DBM; # force into pack
use ExtUtils::Install; # force into pack

# get from local, do whatever is in @INC, install from repo, try again
from local
my @INC_ORIGINAL = @INC;
foreach my $incitem (reverse(@{$ENV{LB_PERL_INC}})) { unshift(@INC, $incitem); }
push(@INC, \&loadModule);
foreach my $incitem (@{$ENV{LB_PERL_INC}}) { push(@INC, $incitem); }

sub installModuleFromRepository
{ # can call this directly to upgrade a module or loadModule() will
call it for you if missing modules
  eval
  { # eval because P::R::C will die if the computer or our repo is offline
    my $module = shift;
    my $par = PAR::Repository::Client->new(
      uri => $ENV{LB_REPO_URI},
      http_timeout => 30,
      auto_upgrade => 1,
      static_dependencies => 1,
    );
    my @INC_WAS = @INC;
    @INC = @INC_ORIGINAL; # letting the original @INC (before we
tweaked it) do the job
    print qq(Wanting to install [$module] from repository...\n);
    my $status = $par->use_module($module); # is use_module the best
way?  want to ensure upgrade/install on demand
    @INC = @INC_WAS;
    return $status;
  };
  return undef; # if return undef it failed (you're probably offline
or the repo is unavailable)
}

sub loadModule
{
  my ($arrayref, $module) = @_;
  $module =~ s/\.pm$//;
  $module =~ s/[\/\\]/::/g;
  installModuleFromRepository($module) if ($module);
  return undef; # claim it failed, let the next step in @INC deal with it
}

# load and run the perl file presented on first arg to loader
my $file = $ARGV[0];
if ($file && -f $file && open(FH, '<', $file))
{
  eval(join('', <FH>)) or die($@);
  close(FH);
}
##############################

############################## file: payload.pl
# (loaded by loader.exe)
# note: you can put any perl code in here as long as the repo has the
modules needed
use Data::Dumper;
use Megagram::ResolveSRV;

my $rsrv = Megagram::ResolveSRV->new;
my @hosts = $rsrv->resolve('_xmpp-server._tcp.google.com');
print Dumper(\...@hosts);

use Sys::Info::OS;
my $os = Sys::Info::OS->new;
my $not = ($os->is_root ? '' : ' not');
print "You are$not an admin.\n";

1;
##############################

############################## file: Config.pm
# (patched lines, replace existing FETCH)
# Windows/Strawberry Perl 5.10.0 version
sub FETCH {
    my($self, $key) = @_;

    # check for cached value (which may be undef so we use exists not defined)
    my $val;
    if (exists $self->{$key})
    {
      $val = $self->{$key};
    }
    else
    {
      $val = $self->fetch_string($key);
    }

    $val =~ s#C:[\\/]strawberry[\\/]perl#$ENV{LB_PERL_DIR}#gi if
($ENV{LB_PERL_DIR});
    return $val;
}
##############################

############################## file: Config.pm
# (patched lines, replace existing FETCH)
# stock Linux Perl 5.10.0 version (at least for Debian/Ubuntu)
sub FETCH {
    my($self, $key) = @_;

    # check for cached value (which may be undef so we use exists not defined)
    my $val;
    if (exists $self->{$key})
    {
        $val = $self->{$key};
    }
    else
    {
      $val = $self->fetch_string($key);
    }

    $val =~ s#/usr#$ENV{LB_PERL_DIR}#gi if ($ENV{LB_PERL_DIR});

    return $val;
}
##############################

Reply via email to