App-Fetchware
view release on metacpan or search on metacpan
lib/App/Fetchware.pm view on Meta::CPAN
# Fetchwarefile that fetchware simply executes with eval.
our @EXPORT = qw(
program
filter
temp_dir
fetchware_db_path
user
prefix
configure_options
make_options
build_commands
install_commands
uninstall_commands
lookup_url
lookup_method
gpg_keys_url
gpg_sig_url
sha1_url
md5_url
user_agent
verify_method
no_install
verify_failure_ok
user_keyring
stay_root
mirror
config
new
new_install
check_syntax
start
lookup
download
verify
unarchive
build
install
end
uninstall
upgrade
hook
);
# These tags allow you to replace some or all of fetchware's default behavior to
# install unusual software.
our %EXPORT_TAGS = (
# No OVERRIDE_START OVERRIDE_END because start() does *not* use any helper
# subs that could be beneficial to override()rs.
OVERRIDE_NEW => [qw(
extension_name
fetchwarefile_name
opening_message
get_lookup_url
download_lookup_url
get_mirrors
get_verification
get_filter_option
append_to_fetchwarefile
prompt_for_other_options
append_options_to_fetchwarefile
edit_manually
)],
OVERRIDE_NEW_INSTALL => [qw(
ask_to_install_now_to_test_fetchwarefile
)],
OVERRIDE_CHECK_SYNTAX => [qw(
check_config_options
)],
OVERRIDE_LOOKUP => [qw(
get_directory_listing
parse_directory_listing
determine_download_path
ftp_parse_filelist
http_parse_filelist
file_parse_filelist
lookup_by_timestamp
lookup_by_versionstring
lookup_determine_downloadpath
)],
OVERRIDE_DOWNLOAD => [qw(
determine_package_path
)],
OVERRIDE_VERIFY => [qw(
gpg_verify
sha1_verify
md5_verify
digest_verify
)],
OVERRIDE_UNARCHIVE => [qw(
check_archive_files
list_files
list_files_tar
list_files_zip
unarchive_package
unarchive_tar
unarchive_zip
)],
OVERRIDE_BUILD => [qw(
run_star_commands
run_configure
)],
OVERRIDE_INSTALL => [qw(
chdir_unless_already_at_path
)],
OVERRIDE_UNINSTALL => [qw()],
OVERRIDE_UPGRADE => [qw()],
);
# OVERRIDE_ALL is simply all other tags combined.
@{$EXPORT_TAGS{OVERRIDE_ALL}} = map {@{$_}} values %EXPORT_TAGS;
# *All* entries in @EXPORT_TAGS must also be in @EXPORT_OK.
our @EXPORT_OK = @{$EXPORT_TAGS{OVERRIDE_ALL}};
###BUGALERT### Add strict argument checking to App::Fetchware's API subroutines
#to check for not being called correctly to aid extension debugging.
lib/App/Fetchware.pm view on Meta::CPAN
my $opening_message = <<EOM;
Fetchware's new command is reasonably sophisticated, and is smart enough to
determine based on the lookup_url you provide if it can autogenerate a
Fetchwarefile for you. If Fetchware cannot, then it will ask you more
questions regarding the information it requires to be able to build a
installable fetchware package for you. After that, fetchware will ask you if
you would like to edit the Fetchwarefile, fetchware has created for you in an
editor. If you say yes, fetchware will open a editor for you, but if you say
no, fetchware will skip the custom editing. Next, fetchware will create a test
Fetchwarefile for you, and ask you if you would like to test it by trying to
install it now. If you say yes, fetchware will install it, and if you say no,
then fetchware will print the location of the Fetchwarefile it created for
you to later use to install your application.
EOM
opening_message($opening_message);
# Ask user for name of program unless the user provided one at command
# line such as fetchware new <programname>.
$program_name = fetchwarefile_name(program => $program_name);
vmsg "Determined name of your program to be [$program_name]";
$fetchwarefile->config_options(program => $program_name);
vmsg "Appended program [$program_name] configuration option to Fetchwarefile";
my $lookup_url = get_lookup_url($term);
vmsg "Asked user for lookup_url [$lookup_url] from user.";
$fetchwarefile->config_options(lookup_url => $lookup_url);
vmsg "Appended lookup_url [$lookup_url] configuration option to Fetchwarefile";
vmsg "Downloaded lookup_url [$lookup_url]";
my $filename_listing = download_lookup_url($term, $lookup_url);
vmsg "Downloaded lookup_url's directory listing";
vmsg Dumper($filename_listing);
my $mirrors_hashref = get_mirrors($term, $filename_listing);
vmsg "Added mirrors to your Fetchwarefile.";
vmsg Dumper($mirrors_hashref);
my $verify_hashref = get_verification($term, $filename_listing, $lookup_url);
vmsg "Added verification settings to Fetchwarefile.";
vmsg Dumper($verify_hashref);
my $filter_hashref = get_filter_option($term, $filename_listing);
vmsg "Added [$filter_hashref->{filter}] filter setting to Fetchwarefile.";
$fetchwarefile->config_options(
%$mirrors_hashref,
%$verify_hashref,
%$filter_hashref
);
###BUGALERT### Ask to parrallelize make with make_options???
###BUGALERT### Verify prefix is writable by current user, who will
#presumably be the user who will install the package now and later.
###BUGALERT### Ask user for a prefix if their running nonroot???
vmsg 'Prompting for other options that may be needed.';
my $other_options_hashref = prompt_for_other_options($term,
temp_dir => {
prompt => <<EOP,
What temp_dir configuration option would you like?
EOP
print_me => <<EOP
temp_dir is the directory where fetchware creates a temporary directory that
stores all of the temporary files it creates while it is building your software.
The default directory is /tmp on Unix systems and C:\\temp on Windows systems.
EOP
},
user => {
prompt => <<EOP,
What user configuration option would you like?
EOP
print_me => <<EOP
user specifies what user fetchware will drop priveleges to on Unix systems
capable of doing so. This allows fetchware to download files from the internet
with user priveleges, and not do anything as the administrative root user until
after the downloaded software package has been verified as exactly the same as
the author of the package intended it to be. If you use this option, the only
thing that is run as root is 'make install' or whatever this package's
install_commands configuratio option is.
EOP
},
prefix => {
prompt => <<EOP,
What prefix configuration option would you like?
EOP
print_me => <<EOP
prefix specifies the base path that will be used to install this software. The
default is /usr/local, which is acceptable for most unix users. Please note that
this difective only works for software packages that use GNU AutoTools, software
that uses ./configure --prefix=<your prefix will go here> to change the prefix.
EOP
},
configure_options => {
prompt => <<EOP,
What configure_options configuration option would you like?
EOP
print_me => <<EOP
configure_options specifies what options fetchware should add when it configures
this software package for you. A list of possible options can be obtained by
running unarchiving the software package that corresponds to this Fetchwarefile,
and running the command './configure --help'. These options vary from software
package to software package. Please note that this option only works for GNU
AutoTools based software distributions, ones that use ./configure to configure
the software.
EOP
},
make_options => {
prompt => <<EOP,
What make_options configuration option would you like?
EOP
print_me => <<EOP
make_options specifies what options fetchware will pass to make when make is run
to compile, perhaps test, and install your software package. They are simpley
added after make is called. An example is '-j 4', which will cause make to
execute 4 jobs simultaneously. A reasonable rule of thumb is to set make's -j
argument to two times as many cpu cores your computer has as compiling programs
is sometimes IO bound instead of CPU bound, so you can get away with running
more jobs then you have cores.
EOP
},
###BUGALERT### Create a config sub called build_system that takes args like
#AutoTools, cmake, MakeMaker, Module::Build, and so on that will use the default
#build commands of whatever system this option specifies.
build_commands => {
prompt => <<EOP,
What build_commands configuration option would you like?
EOP
print_me => <<EOP
build_commands specifies what commands fetchware will run to compile your
software package. Fetchware's default is simply 'make', which is good for most
programs. If you're software package uses something other than fetchware's
default of GNU AutoTools, then you may need to change this configuration option
to specify what you would like instead. Specify multiple build commands in
single quotes with a comma between them:
'./configure', 'make'
EOP
},
install_commands => {
prompt => <<EOP,
What install_commands configuration option would you like?
EOP
print_me => <<EOP
install_commands specifies what commands fetchware will run to install your
software package. Fetchware's default is simply 'make install', which is good
for most programs. If you're software package uses something other than
fetchware's default of GNU AutoTools, then you may need to change this
configuration option to specify what you would like instead. Specify multiple
build commands in single quotes with a comma between them:
'make test', 'make install'
EOP
},
uninstall_commands => {
prompt => <<EOP,
What uninstall_commands configuration option would you like?
EOP
print_me => <<EOP,
uninstall_commands specifes what commands fetchware will run to uninstall your
software pacakge. The default is 'make uninstall,' which works for some GNU
AutoTools packages, but not all. If your software package does not have a 'make
uninstall' make target, but it has some other command that can uninstall it,
then please specify it using uninstall_commands so fetchware can uninstall it.
EOP
},
lookup_method => {
prompt => <<EOP,
What lookup_method configuration option would you like?
EOP
print_me => <<EOP
lookup_method specifies what how fetchware determines if a new version of your
software package is available. The available algorithms are 'timstamp' and
'versionstring'. 'timestamp' uses the timestamp listed in the FTP or HTTP
listing, and uses the software package that is the newest by filesystem
timestamp. The 'versionstring' algorithm uses the filename of the files in the
FTP or HTTP listing. It parses out the version information, sorts it highest to
lowest, and then picks the highest version of your software package. The default
is try 'timestamp' and if that doesn't work, then try 'versionstring'.
EOP
},
gpg_keys_url => {
prompt => <<EOP,
What gpg_keys_url configuration option would you like?
EOP
print_me => <<EOP
gpg_keys_url specifies a url similar to lookup_url in that it should specify a
directory instead a specific file. It is used to download KEYS files, which
contain your program author's gpg keys to import into gpg.
EOP
},
gpg_sig_url => {
prompt => <<EOP,
What gpg_sig_url configuration option would you like?
EOP
print_me => <<EOP
gpg_sig_url specifies a url similar to lookup_url in that it should specify a
directory instead a specific file. It is used to download gpg signatures to
verify your software package.
EOP
},
sha1_url => {
prompt => <<EOP,
What sha1_url configuration option would you like?
EOP
print_me => <<EOP
sha1_url specifies a url similar to lookup_url in that it should specify a
directory instead of a specific file. It is separate from lookup_url, because
you should download software from mirrors, but checksums from the original
vendor's server, because checksums are easily replaced on a mirror by a hacker
if the mirror gets hacked.
EOP
},
md5_url => {
prompt => <<EOP,
What md5_url configuration option would you like?
EOP
print_me => <<EOP,
md5_url specifies a url similar to lookup_url in that it should specify a
directory instead of a specific file. It is separate from lookup_url, because
you should download software from mirrors, but checksums from the original
vendor's server, because checksums are easily replaced on a mirror by a hacker
if the mirror gets hacked.
EOP
},
verify_method => {
prompt => <<EOP,
What verify_method configuration option would you like?
EOP
print_me => <<EOP,
verify_method specifies what method of verification fetchware should use to
ensure the software you have downloaded has not been tampered with. The default
is to try gpg verification, then sha1, and then finally md5, and if they all
fail an error message is printed and fetchware exits, because if your software
package cannot be verified, then it should not be installed. This configuration
option allows you to remove the warnings by specifying a specific way of
verifying your software has not been tampered with. To disable verification set
the 'verify_failure_ok' configuration option to true.
EOP
},
###BUGALERT### replace no_install config su with a command line option that
#would be the opposite of --force???
# Nah! Leave it! Just create a command line option for it too!
no_install => {
prompt => <<EOP,
Would you like to enable the no_install configuration option?
EOP
###BUGALERT### no_install is not currently implemented properly!!!
print_me => <<EOP
no_install is a true or false option, whoose acceptable values include 1
or 0, true or falue, On or Off. It's default value is false, but if you enable
it, then fetchware will not install your software package, and instead it will
simply download, verify, and build it. And then it will print out the full path
of the directory it built your software package in.
EOP
###BUGALERT### Add support for a check regex, so that I can ensure
#that what the user enters will be either true or false!!!
},
verify_failure_ok => {
prompt => <<EOP,
Would you like to enable the verify_failure_ok configuration option?
EOP
print_me => <<EOP
verify_failure_ok is a true or false option, whoose acceptable values include 1
or 0, true or falue, On or Off. It's default value is false, but if you enable
it, then fetchware will not print an error message and exit if verification
fails for your software package. Please note that you should never use this
option, because it makes it possible for fetchware to install source code that
may have been tampered with.
EOP
},
users_keyring => {
prompt => <<EOP,
Would you like to enable users_keyring configuration option?
EOP
print_me => <<EOP
users_keyring when enabled causes fetchware to use the user who calls
fetchware's gpg keyring instead of fetchware's own gpg keyring. Useful for
source code distributions that do not provide an easily accessible KEYS file.
Just remember to import the author's keys into your gpg keyring with gpg
--import.
EOP
},
);
vmsg 'User entered the following options.';
vmsg Dumper($other_options_hashref);
# Append all other options to the Fetchwarefile.
$fetchwarefile->config_options(%$other_options_hashref);
vmsg 'Appended all other options listed above to Fetchwarefile.';
my $edited_fetchwarefile = edit_manually($term, $fetchwarefile);
vmsg <<EOM;
Asked user if they would like to edit their generated Fetchwarefile manually.
EOM
# Generate Fetchwarefile.
if (blessed($edited_fetchwarefile)
and
$edited_fetchwarefile->isa('App::Fetchware::Fetchwarefile')) {
# If edit_manually() did not modify the Fetchwarefile, then generate
# it.
$fetchwarefile = $fetchwarefile->generate();
} else {
# If edit_manually() modified the Fetchwarefile, then do not
# generate it, and replace the Fetchwarefile object with the new
# string that represents the user's edited Fetchwarefile.
$fetchwarefile = $edited_fetchwarefile;
}
# Whatever variables the new() API subroutine returns are written via a pipe
# back to the parent, and then the parent reads the variables back, and
# makes then available to new_install(), back in the parent, as arguments.
return $program_name, $fetchwarefile;
}
sub extension_name {
# Use a state variable to keep $extension_name's value between calls.
state $extension_name;
# If $extension_name has never been touch and is still undef, then allow it
# to be set.
if (not defined $extension_name) {
$extension_name = shift;
# If $extension_name *is* set, and extension_name() was called with an
# argument, which is what defined shift does (shift shifts the first value
# off of @_ (the subroutine argument array), while defined checks to see if
# one was actually defined and provided by the caller.)
} elsif (defined $extension_name and defined shift) {
die <<EOD;
App-Fetchware: extension_name() was called more than once. It is a singleton,
and therefore can only be called once. Please only call it once to set its
value, and then call it repeatedly wherever you need that value. see perldoc
App::Fetchware for more details.
EOD
}
# Return the singleton $extension_name.
return $extension_name;
}
sub opening_message {
my $opening_message = shift;
# Just print the opening message.
print $opening_message;
}
sub fetchwarefile_name {
my ($term, $fetchwarefile_name, $fetchwarefile_name_value) = @_;
my $what_a_fetchwarefile_name_is = <<EOM;
Fetchware uses the $fetchwarefile_name configuration option to name this
specific Fetchwarefile that Fetchware's new command is helping you create.
Since, you did not provide a $fetchwarefile_name on the command line, please
provide one below
EOM
die <<EOD if not defined $fetchwarefile_name;
App-Fetchware: in your call to fetchwarefile_name() you failed to call it with a
defined \$fetchwarefile_name option. The \$fetchwarefile_name option you
specified is [$fetchwarefile_name]. Please specify this option, and try again.
EOD
if (not defined $fetchwarefile_name_value) {
$fetchwarefile_name_value = $term->get_reply(
prompt => q{What would you like to name this specific Fetchwarefile? },
print_me => $what_a_fetchwarefile_name_is,
# This option requires a name, so just pressing return, which would
# yield undef is not acceptable. We need an actual value, so check
# the value to ensure that it is defined.
allow => sub { defined shift @_ ? return 1 : return 0 }
);
}
return $fetchwarefile_name, $fetchwarefile_name_value;
}
sub get_lookup_url {
my $term = shift;
# prompt for lookup_url.
my $lookup_url = $term->get_reply(
print_me => <<EOP,
Fetchware's heart and soul is its lookup_url. This is the configuration option
that tells fetchware where to check what the latest version of your program is.
This version number is then parsed out of the HTTP/FTP/local directory listing,
and compared against the latest installed version to determine when a new
version of your program has been released.
How to determine your application's lookup_url:
1. Go to your application's Web site.
2. Determine the download link for the latest version and copy it with
CTRL-C or right-click it and select "copy".
3. Paste the download link into your browser's URL Location Bar.
4. Delete the filename from the location by starting at the end and deleting
everything to the left until you reach a slash '/'.
* ftp://a.url/downloads/program.tar.gz -> ftp://a.url/downloads/
5. Press enter to access the directory listing on your Application's mirror
site.
6. If the directory listing in either FTP or HTTP format is displayed in
your browser, then Fetchware's default, built-in lookup fuctionality will
probably work properly. Copy and paste this URL into the prompt below, and
Fetchware will download and analyze your lookup_url to see if it will work
properly. If you do not end up with a browser directory listing, then
please see Fetchware's documentation using perldoc App::Fetchware.
EOP
prompt => q{What is your application's lookup_url? },
allow => qr!(ftp|http|file)://!);
return $lookup_url;
}
sub download_lookup_url {
my $term = shift;
my $lookup_url = shift;
my $filename_listing;
eval {
# Use no_mirror_download_dirlist(), because the regular one uses
# config(qw(lookup_url mirror)), which is not known yet.
my $directory_listing = no_mirror_download_dirlist($lookup_url);
# Create a fake lookup_url, because parse_directory_listing() uses it to
# determine the type of *_filename_listing() subroutine to call.
config(lookup_url => $lookup_url);
$filename_listing = parse_directory_listing($directory_listing);
__clear_CONFIG();
# Fix the most annoying bug that ever existed in perl.
# http://blog.twoshortplanks.com/2011/06/06/unexceptional-exceptions-in-perl-5-14/
1;
} or do {
my $lookup_url_failed_try_again = <<EOF;
fetchware: the lookup_url you provided failed because of :
[$@]
Please try again. Try the steps outlined above to determine what your program's
lookup_url should be. If you cannot figure out what it should be please see
perldoc @{[extension_name()]} for additional hints on how to choose a lookup_url.
EOF
$lookup_url = get_lookup_url($term, $lookup_url_failed_try_again);
eval {
# Use no_mirror_download_dirlist(), because the regular one uses
# config(qw(lookup_url mirror)), which is not known yet.
my $dir_list = no_mirror_download_dirlist($lookup_url);
# Create a fake lookup_url, because parse_directory_listing() uses
# it to determine the type of *_filename_listing() subroutine to
# call.
config(lookup_url => $lookup_url);
$filename_listing = parse_directory_listing($dir_list);
__clear_CONFIG();
# Fix the most annoying bug that ever existed in perl.
# http://blog.twoshortplanks.com/2011/06/06/unexceptional-exceptions-in-perl-5-14/
1;
} or do {
die <<EOD;
fetchware: run-time error. The lookup_url you provided [$lookup_url] is not a
usable lookup_url because of the error below:
[$@]
Please see perldoc @{[extension_name()]} for troubleshooting tips and rerun
fetchware new.
EOD
};
};
return $filename_listing;
}
###BUGALERT### Use the $filename_listing argument to search for a MIRRORS file
#that specifies this open source distribution's official listing of mirrors,
#parse it, and add them to the returned hash or mirrors. But, it'll probably
#need configuration. Use GeoIP? No options are avalable. Parse the list, and
#present it to the user, and ask him to pick some:)
sub get_mirrors {
my ($term, $filename_listing) = @_;
my @mirrors;
my $mirror = $term->get_reply(
print_me => <<EOP,
Fetchware requires you to please provide a mirror. This mirror is required,
because most software authors prefer users download their software packages from
a mirror instead of from the authors main download site, which your lookup_url
should point to.
The mirror should be a URL in standard browser format such as [ftp://a.mirror/].
FTP, HTTP, and local file:// mirrors are supported. All other formats are not
supported.
EOP
prompt => 'Please enter the URL of your mirror: ',
allow => qr!^(ftp|http|file)://!,
);
# Append mirror to $fetchwarefile.
push @mirrors, $mirror;
if (
$term->ask_yn(
print_me => <<EOP,
In addition to the one required mirror that you must define in order for
fetchware to function properly, you may specify additonal mirros that fetchware
will use if the mirror you've already specified is unreachable or download
attempts using that mirror fail.
EOP
prompt => 'Would you like to add any additional mirrors? ',
default => 'n',
)
) {
# Prompt for first mirror outside loop, because if you just hit enter or
# type done, then the above text will be appended to your fetchwarefile,
# but you'll be able to skip actually adding a mirror.
my $first_mirror = $term->get_reply(
prompt => 'Type in URL of mirror or done to continue: ',
allow => qr!^(ftp|http|file)://!,
);
# Append $first_mirror to $fetchwarefile.
push @mirrors, $first_mirror;
while (1) {
my $mirror_or_done = $term->get_reply(
prompt => 'Type in URL of mirror or done to continue: ',
default => 'done',
allow => qr!(^(ftp|http|file)://)|done!,
);
if ($mirror_or_done eq 'done') {
last;
} else {
# Append $mirror_or_done to $fetchwarefile.
push @mirrors, $mirror_or_done;
}
}
}
return {mirror => \@mirrors};
}
sub get_verification {
my ($term, $filename_listing, $lookup_url) = @_;
my %options;
my %available_verify_methods;
# Determine what types of verification are available.
for my $file_and_timestamp (@$filename_listing) {
if ($file_and_timestamp->[0] =~ /\.(asc|sig|sign)$/) {
$available_verify_methods{gpg}++;
} elsif ($file_and_timestamp->[0] =~ /\.sha1?$/) {
$available_verify_methods{sha1}++;
} elsif ($file_and_timestamp->[0] =~ /\.md5$/) {
$available_verify_methods{md5}++;
}
}
my $verify_configed_flag = 0;
#If gpg is available prefer it over the others.
if (exists $available_verify_methods{gpg}
and defined $available_verify_methods{gpg}
and $available_verify_methods{gpg} > 0
) {
msg <<EOM;
gpg digital signatures found. Using gpg verification.
EOM
$options{verify_method} = 'gpg';
# Search for a KEYS file to use to import the author's keys.
if (grep {$_->[0] eq 'KEYS'} @$filename_listing) {
msg <<EOM;
KEYS file found using lookup_url. Adding gpg_keys_url to your Fetchwarefile.
EOM
# Add 'KEYS' or '/KEYS' to $lookup_url's path.
my ($scheme, $auth, $path, $query, $fragment) =
uri_split($lookup_url);
$path = catfile($path, 'KEYS');
$lookup_url = uri_join($scheme, $auth, $path, $query, $fragment);
$options{gpg_keys_url} = $lookup_url;
$verify_configed_flag++;
} else {
msg <<EOM;
KEYS file *not* found!
EOM
# Since autoconfiguration of KEYS failed, try asking the user if
# they would like to import the author's key themselves into their
# own keyring and have fetchware use that.
if (
$term->ask_yn(prompt =>
q{Would you like to import the author's key yourself after fetchware completes? },
default => 'n',
print_me => <<EOP,
Automatic KEYS file discovery failed. Fetchware needs the author's keys to
download and import into its own keyring, or you may specify the option
user_keyring, which if true will cause fetchware to use the user who runs
fetchware's keyring instead of fetchware's own keyring. But you, the user, needs
to import the author's keys into your own gpg keyring. You can do this now in a
separate shell, or after you finish configuring this Fetchwarefile. Just run the
command [gpg --import <name of file>].
EOP
)
) {
$options{user_keyring} = 'On';
$verify_configed_flag++;
}
# And if the user does not want to, then fallback to sha1 and/or md5
# if they're defined, which is done below.
}
}
# Only try sha1 and md5 if gpg failed.
unless ($verify_configed_flag == 1) {
if (exists $available_verify_methods{sha1}
and defined $available_verify_methods{sha1}
and $available_verify_methods{sha1} > 0
) {
msg <<EOM;
SHA1 checksums found. Using SHA1 verification.
EOM
$options{verify_method} = 'sha1';
} elsif (exists $available_verify_methods{md5}
and defined $available_verify_methods{md5}
and $available_verify_methods{md5} > 0
) {
msg <<EOM;
MD5 checksums found. Using MD5 verification.
EOM
$options{verify_method} = 'md5';
} else {
# Print a huge long nasty warning even include links to news stories
# of mirrors actually getting hacked and serving malware, which
# would be detected and prevented with proper verification enabled.
# Ask user if they would like to continue installing fetchware even if
# verification fails, and then enable the verify_failure_ok option.
if (
$term->ask_yn(prompt => <<EOP,
Would you like fetchware to ignore the fact that it is unable to verify the
authenticity of any downloads it makes? Are you ok with possibly downloading
viruses, worms, rootkits, or any other malware, and installing it possibly even
as root?
EOP
default => 'n',
print_me => <<EOP,
Automatic verification of your fetchware package has failed! Fetchware is
capable of ignoring the error, and installing software packages anyway using its
verify_failure_ok configuration option. However, installing software packages
without verifying that they have not been tampered with could allow hackers to
potentially install malware onto your computer. Don't think this is *not*
possible or do you think its extremely unlikely? Well, it's actually
surprisingly common:
1. http://arstechnica.com/security/2012/09/questions-abound-as-malicious-phpmyadmin-backdoor-found-on-sourceforge-site/
Discusses how a mirror for sourceforge was hacked, and the phpMyAdmin
software package on that mirror was modified to spread malware.
2. http://www.geek.com/news/major-open-source-code-repository-hacked-for-months-says-fsf-551344/
Discusses how FSF's gnu.org ftp download site was hacked.
3. http://arstechnica.com/security/2012/11/malicious-code-added-to-open-source-piwik-following-website-compromise/
Discusses how Piwiki's wordpress software was hacked, and downloads of
Piwiki had malicious code inserted into them.
4. http://www.theregister.co.uk/2011/03/21/php_server_hacked/
Discusses how php's wiki.php.org server was hacked yielding credentials to
php's source code repository.
Download mirrors *do* get hacked. Do not make the mistake, and think that it is
not possible. It is possible, and it does happen, so please properly configure
your Fetchwarefile to enable fetchware to verify that the downloaded software is
the same what the author uploaded.
EOP
)
) {
# If the user is ok with not properly verifying downloads, then
# ignore the failure, and install anyway.
$options{verify_failure_ok} = 'On';
} else {
# Otherwise, throw an exception.
die <<EOD;
fetchware: Fetchware *must* be able to verify any software packages that it
downloads. The Fetchwarefile that you were creating could not do this, because
you failed to specify how fetchware can verify its downloads. Please rerun
fetchware new again, and this time be sure to specify a gpg_keys_url, specify
user_keyring to use your own gpg keyring, or answer yes to the question
regarding adding verify_failure_ok to your Fetchwarefile to make failing
verificaton acceptable to fetchware.
EOD
}
}
}
return \%options;
}
sub get_filter_option {
my $term = shift;
# $filename_listing is an array of [$filename, $timestamp] arrays.
my $filename_listing = shift;
msg <<EOS;
Analyzing the lookup_url you provided to determine if fetchware can use it to
successfully determine when new versions of your software are released.
EOS
my $filter;
if (grep {$_->[0] =~ /^(CURRENT|LATEST)[_-]IS[_-].+/} @$filename_listing) {
# There is only one version in the lookup_url directory listing, so
# I do not need a filter option.
msg <<EOS;
* The lookup_url you gave fetchware includes a CURRENT_IS or a LATEST_IS file
that tells fetchware and regular users what the latest version is. Because of
this we can be reasonable sure that a filter option is not needed, so I'll skip
asking for one. You can provide one later if you need to provide one, when
fetchware prompts you for any custom options you may want to use.
EOS
} else {
# There is a CURRENT_IS_<ver_num> or LATEST_IS_<ver_num> file that tells
# you what the latest version is.
###BUGALERT### Why is this line in both sections of the if statement??? Inside
#this else block means that a CURRENT_IS or LATEST-IS was *not* found??? Fix
#this!!!!!!
msg <<EOS;
* The directory listing of your lookup_url has a CURRENT_IS_<ver_num> or
LATEST_IS_<ver_num> file that specifies the latest version, which means that
your program's corresponding Fetchwarefile does not need a filter option. If you
still would like to provide one, you can do so later on, when fetchware allows
you to define any additional configuration options.
EOS
my $what_a_filter_is = <<EOA;
Fetchware needs you to provide a filter option, which is a pattern that fetchware
compares each file in the directory listing of your lookup_url to to determine
which version of your program to install.
Directories will have other junk files in them or even completely different
programs that could confuse fetchware, and even potentially cause it to install
a different program. Therefore, you should also add the program name to the
begining of your filter. For example if you program is apache, then your filter
should include the name of apache on mirror sites, which is actually:
httpd
For example, Apache's lookup_url has three versions in the same lookup_url
directory listing. These are 2.4, 2.2, and 2.0. Without the filter option
fetchware would choose the highest, which would be 2.4, which is the latest
version. However, you may want to stick with the older and perhaps more stable
2.2 version of apache. Therefore, you'll need to tell fetchware this by using
by adding the version number to your filter:
httpd-2.2
will result in fetchware filtering the results of its lookup check through your
filter of httpd-2.2 causing fetchware to choose the latest version from the 2.2
stable branch instead of the higher version numbered 2.4 or 2.0 legacy releases.
Note the use of the dash, which is used in the filename to separate the 'httpd'
name part from the '2.2' version part.
Note: fetchware accepts any valid perl regular expresion as an acceptable
filter option, but that should only be needed for advanced users. See perldoc
fetchware.
EOA
# Prompt for the needed filter option.
$filter = $term->get_reply(
prompt => <<EOP,
[Just press enter or return to skip adding a filter option]
What does fetchware need your filter option to be?
EOP
print_me => $what_a_filter_is,
);
###BUGALERT### Consider Adding a loop around checking the filter option
#that runs determine_lookup_url() using the provided filter option, and
#then asking the user if that is indeed the correct filter option, and
#if not ask again and try it again unit it succeeds or user presses
#ctrl-c|z.
}
return {filter => $filter};
}
sub prompt_for_other_options {
my $term = shift;
my %option_description = @_;
my %answered_option;
if (
$term->ask_yn(prompt =>
q{Would you like to add extra configuration options to your fetchwarefile?},
default => 'n',
print_me => <<EOP,
Fetchware has many different configuration options that allow you to control its
behavior, and even change its behavior if needed to customize fetchware for any
possible source code distribution.
If you think you need to add configuration options please check out perldoc
fetchware for more details on fetchware and its Fetchwarefile configuration
options.
If this is your first package your creating with Fetchware or you're creating a
package for a new program for the first time, you should skip messing with
fetchware's more flexible options, and just give the defaults a chance.
EOP
)
) {
my @options = keys %option_description;
my @config_file_options_to_provide = $term->get_reply(
print_me => <<EOP,
Below is a listing of Fetchware's available configuration options.
EOP
prompt => <<EOP,
Please answer with a space seperated list of the number before the configuration
file options that you would like to add to your configuration file?
EOP
choices => \@options,
multi => 1,
);
for my $config_file_option (@config_file_options_to_provide) {
$answered_option{$config_file_option} = $term->get_reply(
print_me => $option_description{$config_file_option}->{print_me},
prompt => $option_description{$config_file_option}->{prompt},
);
}
}
return \%answered_option;
}
sub edit_manually {
my ($term, $fetchwarefile) = @_;
if (
$term->ask_yn(
print_me => <<EOP,
Fetchware has now asked you all of the needed questions to determine what it
thinks your new program's Fetchwarefile should look like. But it's not perfect,
and perhaps you would like to tweak it manually. If you would like to edit it
manually in your favorite editor, answer 'yes', and if you want to skip this just
answer 'no', or just press <Enter>.
If you would like to cancel any edits you have made, and use the automagically
generated Fetchwarefile, just delete the entire contents of the file, and save
an empty file.
EOP
prompt => q{Would you like to edit your automagically generated Fetchwarefile manually? },
default => 'n',
)
) {
my ($fh, $fetchwarefile_filename) =
tempfile('Fetchwarefile-XXXXXXXXX', TMPDIR => 1);
print $fh $fetchwarefile->generate();
close $fh;
# Ask what editor to use if EDITOR environment variable is not set.
my $editor = $ENV{EDITOR} || do {
$term->get_reply(prompt => <<EOP,
What text editor would you like to use?
EOP
print_me => <<EOP
The Environment variable EDITOR is not set. This is used by fetchware and other
programs to determine what program fetchware should use to edit your
Fetchwarefile. Please enter what text editor you would like to use. Examples
include: vim, emacs, nano, pico, or notepad.exe (on Windows).
EOP
);
};
run_prog($editor, $fetchwarefile_filename);
# NOTE: fetchware will "block" during the above call to run_prog(), and
# wait for the user to close the editor program.
# If the edited Fetchwarefile does not have a file size of zero.
if (not -z $fetchwarefile_filename) {
my $fh = safe_open($fetchwarefile_filename, <<EOD);
fetchware: run-time error. fetchware can't open the fetchwarefile you edited
with your editor after you edited it. This just shouldn't happen. Possible race
condition or weird bug. See perldoc fetchware.
EOD
# Since the generated Fetchwarefile has been edited, because its
# size is nonzero, then replace the App::Fetchware::Fetchwarefile
# object with whatever text can be slurped from the file the user
# edited. Since it is now a scalar instead of an object, that is how
# Fetchware will tell if the user changed it or not.
$fetchwarefile = do { local $/; <$fh> }; # slurp fetchwarefile
} else {
msg <<EOM;
You canceled any custom editing of your fetchwarefile by writing an empty file
to disk.
EOM
}
}
return $fetchwarefile;
}
sub new_install {
my ($term, $program_name, $fetchwarefile) = @_;
my $fetchware_package_path =
ask_to_install_now_to_test_fetchwarefile($term, \$fetchwarefile,
$program_name);
return $fetchware_package_path;
}
sub ask_to_install_now_to_test_fetchwarefile {
my ($term, $fetchwarefile, $program_name) = @_;
vmsg <<EOM;
Determining if user wants to install now or just save their Fetchwarefile.
EOM
# If the user wants to install their new Fetchwarefile.
if (
$term->ask_yn(
print_me => <<EOP,
It is recommended that fetchware go ahead and install the package based on the
Fetchwarefile that fetchware has created for you. If you don't want to install
it now, then enter 'no', but if you want to test your Fetchwarefile now, and
install it, then please enter 'yes' or just press <Enter>.
EOP
prompt => q{Would you like to install the package you just created a Fetchwarefile for? },
default => 'y',
)
) {
# Create a temp Fetchwarefile to store the autogenerated configuration.
my ($fh, $fetchwarefile_filename)
=
tempfile("fetchware-$$-XXXXXXXXXXXXXX", TMPDIR => 1, UNLINK => 1);
print $fh $$fetchwarefile;
# Close the temp file to ensure everything that was written to it gets
# flushed from caches and actually makes it to disk.
close $fh;
vmsg <<EOM;
Saved Fetchwarefile temporarily to [$fetchwarefile_filename].
EOM
# Reach up bin/fetchware's skirt, and call cmd_install directly, because
# if I use system() and call fetchware again in a separate process using
# the install command, it will return a useless number indicating
# success instead of the $fetchware_package_path I want. I could parse
# the output, but that's a head ache I want to avoid. Instead, I'll just
# be a little frisky.
my $fetchware_package_path = fetchware::cmd_install($fetchwarefile_filename);
###BUGALERT### Call cmd_install() inside an eval that will catch any
#problems that come up, and suggest how to fix them???
#Is that really doable???
vmsg <<EOM;
Copied Fetchwarefile package to fetchware database [$fetchware_package_path].
EOM
msg 'Installed Fetchware package to fetchware database.';
return $fetchware_package_path;
# Else the user just wants to save the Fetchwarefile somewhere.
} else {
my $fetchwarefile_filename = $program_name . '.Fetchwarefile';
# Get a name for the Fetchwarefile that does not already exist.
if (-e $fetchwarefile_filename) {
while (1) {
$fetchwarefile_filename = $term->get_reply(
prompt => <<EOP,
What would you like your new Fetchwarefile's filename to be?
EOP
print_me => <<EOP
Fetchware by default uses the program name you specified at the beginning of
running fetchware new plus a '.Fetchwarefile' extension to name your
Fetchwarefile. But his file already exists, so you'll have to pick a new
filename that does not currently exist.
EOP
);
last unless -e $fetchwarefile_filename;
}
}
vmsg <<EOM;
Determine Fetchwarefile name to be [$fetchwarefile_filename].
EOM
###BUGALERT### Replace >, create or delete whole file and replace it with
#what I write now, with >> for append to file if it already exists????
###BUGALERT### Should safe_open() be moved into the loop above, and instead
#of checking for existence, open the file using safeopen as needed, but
#don't write to it just yet, and then test the open file handle if it's
#empty, and therefore presumable a new file, or an old file that no one
#cares about anymore, because it's empty?
my $fh = safe_open($fetchwarefile_filename, <<EOD, MODE => '>');
fetchware: failed to open your new fetchwarefile because of os error
[$!]. This really shouldn't happen in this case. Probably a bug, or a weird race
condition.
EOD
print $fh $$fetchwarefile;
close $fh;
msg "Saved Fetchwarefile to [$fetchwarefile_filename].";
return $fetchwarefile_filename;
}
}
sub start {
my %opts = @_;
# Add temp_dir config sub to create_tempdir()'s arguments.
if (config('temp_dir')) {
$opts{TempDir} = config('temp_dir');
vmsg "Using user specified temporary directory [$opts{TempDir}]";
}
# Add KeepTempDir option if no_install is set. That way user can still
# access the build directory to do the install themselves.
if (config('no_install')) {
$opts{KeepTempDir} = 1;
vmsg "no_install option enabled not deleting temporary directory.";
}
# Forward opts to create_tempdir(), which does the heavy lifting.
my $temp_dir = create_tempdir(%opts);
msg "Created fetchware temporary directory [$temp_dir]";
lib/App/Fetchware.pm view on Meta::CPAN
=item L<config_replace()|App::Fetchware::Config/config_replace()> - config() is
used to I<set> configuration options, and once set they I<cannot> be changed by
config(). This is meant to catch and reduce errors. But sometimes, mostly in
test suites, you need to change the value of a configuration option. That's
what config_replace() is for.
=item L<config_delete()|App::Fetchware::Config/config_delete()> - deletes the
specified configuration option. Mostly just used for testing.
=item L<__clear_CONFIG()|App::Fetchware::Config/__clear_CONFIG()> - An internal
only subroutine that should be only used when it is really really needed. It
I<clears> (deletes) the entire internal hash that the configuration options are
stored in. It really should only be used during testing to clear
App::Fetchware::Config's intenal state between tests.
=item L<debug_CONFIG()|App::Fetchware::Config/debug_CONFIG()> - prints
App::Fetchware::Config's internal state directly to STDOUT. Meant for debugging
only in your test suite.
=back
=item L<App::Fetchware::Fetchwarefile>
Helper OO class for new() API subroutine. Allows you to programatically build a
Fetchwarefile for the user using a small API instead of manually concatenating
tons of strings constantly worrying about wordwrap and whitespace.
=over
=item L<new() constructor|App::Fetchware::Fetchwarefile/new()> -
App::Fetchware::Fetchwarefile is actually Object-oriented unlike the rest of
Fetchware's internals, so new() is its constructor.
=item L<config_options()|App::Fetchware::Fetchwarefile/config_options()> - Used
to add the actual configuration options and their values to the Fetchwarefile
object.
=item L<generate()|App::Fetchware::Fetchwarefile/generate()> - Returns the
"generated" Fetchwarefile. This method does all of the string concatenation for
you in order to create the Fetchwarefile specfied in your new() and
config_options() calls.
=back
=item L<App::Fetchware's OVERRIDE_* export tags.|FETCHWAREFILE API SUBROUTINES>
App::Fetchware's main API subroutines, especially the crazy complicated ones
such as lookup(), are created by calling and passing data among many component
subroutines. This is done to make testing much much easier, and to allow
App::Fetchware extensions to also use some or most of these component
subroutines when they override a App::Fetchware API subroutine.
=over
=item L<new()'s OVERRIDE_NEW export tag.|new() API REFERENCE>
The export tag exports all of the helper subroutines new() uses to implement its
functionality. Some like get_lookup_url(), get_verification(), and
get_filter_option() are quite specific to App::Fetchware, but extension_name,
fetchwarefile_name(), opening_message(), prompt_for_other_options(), and
edit_manually() are nice and generic, and should be appropriate for any
Fetchware extension.
=item L<new_install()'s OVERRIDE_NEW_INSTALL export tag.|new_install() API REFERENCE>
new_install() only exports ask_to_install_now_to_test_fetchwarefile(), and most
Fetchware extensions should probably just "inherit" new_install(), because
new_install()'s only real purpose was making
ask_to_install_now_to_test_fetchwarefile() work when fetchware drops privs.
=item L<check_syntax()'s OVERRIDE_CHECK_SYNTAX export tag|check_syntax() API REFERENCE>
check_syntax() only has the check_config_options() helper subroutine that is
meant for reuse by Fetchware extensions, so they can check their syntax just as
Fetchware itself does.
=item L<lookup()'s OVERRIDE_LOOKUP export tag.|lookup() API REFERENCE>
This export tag is the largest, and perhaps the most important, because it
implements fetchware's ability to determine if a new version of your software
package is available. Its default is just a clever use of HTTP and FTP directory
listings.
See the section L<lookup() API REFERENCE> for more details on how to use these
subroutines to determine if new versions of your software is available
automatically.
=item L<download()'s OVERRIDE_DOWNLOAD export tag.|download() API REFERENCE>
Only exports the subroutine determine_package_path(), which simply comcatenates
a $tempdir with a $filename to return a properl $package_path, which unarchive
later uses. This is mostly its own subroutine to better document how this is
done, and to allow easier code reuse.
=item L<verify()'s OVERRIDE_VERIFY export tag.|verify() API REFERENCE>
Exports a family of subroutines to verify via MD5, SHA1, or GPG the integrity of
your downloaded package. MD5 and SHA1 are supported for legacy reasons. All
software packages should be GPG signed for much much much better security. GPG
signatures when verified actually prove that the software package you downloaded
is exactly what the author of that software package created, whereas MD5 and
SHA1 sums just verify that you downloaded the bunch of bits in the same order
that they are stored on the server.
digest_verify() an be used to add support for any other Digest::* modules that
CPAN has a Digest based module for that correctly follow Digest's API.
=item L<unarchive()'s OVERRIDE_UNARCHIVE export tag.|unarchive() API REFERENCE>
Exports subroutines that will help you unarchive software packages in tar and
zip format. The most important part to remember is to use list_files() to list
the files in your archive, and pass that list to check_archive_files() to ensure
that the archive will not overwrite any system files, and contains no absolute
paths that could cause havok on your system. unarchive_package() does the actual
unarchiving of software packages.
=item L<build()'s OVERRIDE_BUILD export tag.|build() API REFERENCE>
Provides run_star_commands(), which is meant to execute common override commands
that fetchware provides with the C<build_commands>, C<install_commands>, and
lib/App/Fetchware.pm view on Meta::CPAN
=head3 get_verification()
my $verification_hashref = get_verification($term, $filename_listing, $lookup_url);
# $verification_hashref = (
# gpg_keys_url => 'http://main.mirror/distdir',
# verification_method => 'gpg',
# );
Parses $filename_listing to determine what type of verification is available.
Prefering gpg, but falling back on sha1, and then md5 if gpg is not available.
If the type is gpg, then get_verification() will ask the user to specify a
C<gpg_keys_url>, which is required for gpg, because fetchware needs to be able
to import the needed keys to be able to use those keys to verify package
downloads. If this URL is not provided by the author, then get_verification()
will ask the user if they would like to import the author's key into their own
gpg public keyring. If they would, then get_verification() will use the
C<user_keyring> C<'On'> option to use the user's public keyring instead of
fetchware's own keyring. And if the user does not want to use their own gpg
public keyring, then get_verification will fall back to sha1 or md5 setting
C<verify_method> to sha1 or md5 as needed.
Also, adds a gpg_keys_url option if a C<KEYS> file is found in
$filename_listing.
If no verification methods are available, fetchware will print a big nasty
warning message, and offer to use C<verify_failure_ok> to make such a failure
cause fetchware to continue installing your software.
Returns a hashref of options for the user's Fetchwarefile. You're responsible
for calling append_options_to_fetchwarefile() to add them to the user's
Fetchwarefile, or perhaps the caller could analyze them in some way, before
adding them if needed. The keys are the names of the configuration options, and
the values are their values.
=head3 get_filter_option()
$filter_hashref = get_filter_option($term, $filename_listing);
# $filter_hashref = (
# filter => 'user specfied filter option',
# );
Analyzes $filename_listing and asks the user whatever questions are needed by
fetchware to determine if a C<filter> configuration option is needed, and if it
is what it should be. C<filter> is simply a perl regex that the list of files
that fetchware downloads is checked against, and only files that match this
regex will fetchware consider to be the latest version of the software package
that you want to install. The C<filter> option is needed, because some mirrors
will have multiple software packages in the same directory or multitple
different versions of one piece of software in the same directory. An example
would be Apache, which has Apache versions 2.0, 2.2, and 2.4 all in the same
directory. The C<filter> option is how you differentiate between them.
If a filter was provided by the user than it is returned as a hashref with
C<filter> as the key for use with append_options_to_fetchwarefile(), or for
further analysis by extension authors.
=head3 prompt_for_other_options()
prompt_for_other_options($term,
temp_dir => {
prompt => <<EOP,
What temp_dir configuration option would you like?
EOP
print_me => <<EOP
temp_dir is the directory where fetchware creates a temporary directory that
stores all of the temporary files it creates while it is building your software.
The default directory is /tmp on Unix systems and C:\\temp on Windows systems.
EOP
},
...
);
Accepts a Term::Readline/Term::UI object as an argument to use to ask the user
questions, and a gigantic hash of hashes in list form. The hash of hashes,
%option_description, argument incluedes the C<prompt> and C<print_me> options
that are then passed through to Term::UI to ask the user what argument they want
for each specified option in the %option_description hash.
The user's answers are tallied up an returned as a hash reference.
=head3 edit_manually()
$fetchwarefile = edit_manually($term, $fetchwarefile);
edit_manually() asks the user if they would like to edit the specified
$fetchwarefile manually. If the user answers no, then nothing is done. But if
the user answers yes, then fetchware will open their favorit editor either using
the C<$ENV{EDITOR}> environment variable, or fetchware will ask the user what
editor they would like to use. Then this editor, and a temporary fetchwarefile
are opened, and the user can edit their Fetchwarefile as they please. If they
are not satisfied with their edits, and wan to undo them, they can delete the
entire file, and write a size 0 file, which will cause fetchware to ignore the
file they edited. If the write a file with a size greater than 0, then the file
the user wrote, will be used as their Fetchwarefile.
=head2 new_install()
my $fetchware_package_path = new_install($program_name, $fetchwarefile);
=over
=item Configuration subroutines used:
=over
=item All of them, because it calls bin/fetchware's cmd_install(),
which in turn calls all of the API subroutines that fetchware's install command
does.
=back
=back
Exists separate from new(), because new() drops privileges like most other
fetchware commands do. But the new command includes the ability to ask the user
if they want to install the associated program from their newly created
Fetchwarefile, which requires root privileges. Therefore, we must also have a
API subroutine that runs in the root privileged parent to that the install
commands will run with proper permissions when fetchware is run as root.
=over
=item drop_privs() NOTES
This section notes whatever problems you might come accross implementing and
debugging your Fetchware extension due to fetchware's drop_privs mechanism.
See L<Util's drop_privs() subroutine for more info|App::Fetchware::Util/drop_privs()>.
=over
=item *
When fetchware is run as root, new_install() is called in the parent process
with root permissions so that you can call the
( run in 1.693 second using v1.01-cache-2.11-cpan-6aa56a78535 )