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; } ##############################