Script Installers
A Virtualmin script installer is a small program that contains the information needed to install a web application into a virtual server's home directory, and configure it to run with that server's permissions and using its database. Most script installers are for PHP programs like phpMyAdmin, Drupal and SugarCRM, but it is possible to write an installer for Perl, Python or Ruby or Rails applications too.
Virtualmin Pro ships with a large number of built-in installers, which domain owners can add to their websites using the Install Scripts link on the left menu. However, there are many applications that are not covered yet, simply because we don't have time to implement installers for them or they are judged too rarely used or too specific. For this reason, Virtualmin provides an API for adding your own script installers.
Script Installer Files and Directories
Each script installer is a single file containing a set of Perl functions. Those that ship with Virtualmin Pro can be found in the virtual-server/scripts
directory under the Webmin root, which is usually /usr/libexec/webmin
or /usr/share/webmin
. If you open up one of those files (such as phpbb.pl
) in a text editor, you will see a series of funtions like :
sub script_phpbb_desc { return "phpBB"; } sub script_phpbb_uses { return ( "php" ); } sub script_phpbb_longdesc { return "A high powered, fully scalable, and highly customizable Open Source bulletin board package."; }
Your own script installers will be in files if a similar format - the major difference will be the script ID, which appears in each function name after the word script_
.
Script installers that are local to your Virtualmin installation are stored in the /etc/webmin/virtual-server/scripts
directory. In most cases, each script is just a single .pl
file, but it is possible for other source or support files to be part of the script too. In general though, most script installers download the files they need from the website of the application that they are installing.
Script Installer IDs
Every script installer has a unique ID, which must consist of only letters, numbers and the underscore character. The ID determines both the installer filename (which must be scriptid.pl
), and the names of functions within the script (which must be like script_
scriptid_desc
).
The same ID cannot be used by two different installers on the same system, even if one is built-in to Virtualmin and one is custom. For this reason, when writing an installer you should select an ID that is unlikely to clash with any that might be included in Virtualmin in the future. Starting it with the first part of your company's domain name (like foocorp_billingapp) would be a good way to ensure this.
The Lifetime of a Script
Virtualmin allows multiple instances of a single script to be installed, either on different domains or in different directories of the same domain. The installer defines the steps that must be taken to setup a script in some directory - in object-oriented coding parlance, it is like a class, while installed scripts are objects.
When a script is installed via the web interface, Virtualmin performs the following steps :
-
Checks if all required dependencies are satisfied, such as required commands, a database and a website.
-
If the script uses PHP, checks that the versions it supports are available on the system.
-
Displays a form asking for installation options, such as the destination directory and database.
-
Parses inputs from the form.
-
Checks if the same script is already installed in the selected directory.
-
Configures the domain's website to use the correct PHP version.
-
Downloads files needed by the script, such as its source code.
-
Installs any needed PHP modules, Pear modules, Perl modules or Ruby Gems.
-
Calls the script's install function. This typically does the following :
-
Creates a database for the script, if needed and if requested.
-
Creates the destination directory.
-
Extracts the downloaded source into a temporary directory.
-
Copies the source to the destination directory.
-
Updates any config files used by the application being installed, so that it knows how to connect to the database and which directory it runs in.
-
Records the fact that the script has been installed.
-
Configures PHP for the domain, to set any options that the script has requested.
-
Restarts Apache.
Script Installer Implementation
In this section, the functions that each script installer must implement will be covered. Not all functions are mandatory - some deal with PHP dependencies that make no sense if your script does not use PHP, or if it has no non-core module or Pear dependencies. The example code for each function is taken from the WordPress Blog installer, in wordpress.pl
. This is a PHP application whose installation process is relatively simple, yet common to many other PHP programs.
In your own script, you would of course replace scriptname
with the script ID you have selected.
Also, just like a Perl module -- make sure your Install Script file ends with the line:
1;
script_scriptname_desc
This function must return a short name for the script, usually a couple of words at most.
sub script_wordpress_desc { return "WordPress"; }
script_scriptname_uses
This must return a list of the languages the script uses. Supported language codes are php, perl and ruby. Most scripts will return only one.
sub script_wordpress_uses { return ( "php" ); }
script_scriptname_versions
Must return a list of versions of the script that the installer supports. Most can only install one, but in some cases you may want to offer the user the ability to install development and stable versions of some application. The version the user chooses will be passed to many other functions as a parameter.
sub script_wordpress_versions { return ( "2.2.1" ); }
script_scriptname_category (optional)
This function should return a category for the script, which controls how it is categorized in Virtualmin's list of those available to install. At the time of writing, available categories were Blog, Calendar, Commerce, Community, Content Management System, Database, Development, Email, Guestbook, Helpdesk, Horde, Photos, Project Management, Survey, Tracker and Wiki.
sub script_wordpress_category { return "Blog"; }
script_scriptname_php_vers (PHP scripts only)
Scripts that use PHP must implement this function, which should return a list of versions the installed application can run under. At the time of writing, Virtualmin only supports PHP versions 4 and 5. On systems that have more than one version of PHP installed, Virtualmin will configure the website to use the correct version for the path the script is installed to.
sub script_wordpress_php_vers { return ( 4, 5 ); }
script_scriptname_php_modules (PHP scripts only)
If the application being installed is written in PHP and requires any non-core PHP modules, this function should return them as a list. Any script that talks to a MySQL database will need the mysql
module, or pgsql
if it uses PostgreSQL. Virtualmin will attempt to install the required modules if they are missing from the system.
sub script_wordpress_php_modules { return ("mysql"); }
script_scriptname_pear_modules (PHP scripts only)
Pear is a repository of additional modules for PHP, which some Virtualmin scripts make use of. If the application you are installing requires some Pear modules, this function can be implemented to return a list of module names. At installation time, Virtualmin will check for and try to automatically install the needed modules.
sub script_horde_pear_modules { return ("Log", "Mail", "Mail_Mime", "DB"); }
script_scriptname_perl_modules (Perl scripts only)
For scripts written in Perl that require modules that are not part of the standard Perl distribution, you should implement this function to return a list of additional modules required. Virtualmin will try to automatically install them from YUM, APT or CPAN where possible, and will prevent the script from being installed if they are missing.
sub script_twiki_perl_modules { return ( "CGI::Session", "Net::SMTP" ); }
script_scriptname_python_modules (Python scripts only)
For scripts written in Python that require modules that are not part of the standard distribution, you should implement this function to return a list of additional modules required. Virtualmin will try to automatically install them from YUM or APT where possible, and will prevent the script from being installed if they are missing.
sub script_django_python_modules { return ( "setuptools", "MySQLdb" ); }
script_scriptname_depends(&domain, version)
This function must check for any dependencies the script has before it can be installed, such as a MySQL database or virtual server features. It is given two parameters - the domain
hash containing details of the virtual server being installed into, and the version
number selected.
sub script_wordpress_depends { local ($d, $ver) = @_; &has_domain_databases($d, [ "mysql" ]) || return "WordPress requires a MySQL database" if (!@dbs); &require_mysql(); if (&mysql::get_mysql_version() < 4) { return "WordPress requires MySQL version 4 or higher"; } return undef; }
As of Virtualmin 3.57, this function can return a list of missing dependency error messages instead of a single string, which is more user-friendly as they are all reported to users at once.
script_scriptname_dbs(&domain, version)
If defined, this function should return a list of database types that the script can use. At least one of these types must be enabled in the virtual server the script is being installed into.
sub script_wordpress_dbs { local ($d, $ver) = @_; return ("mysql"); }
Only Virtualmin 3.57 and above make use of this function.
script_scriptname_params(&domain, version, &upgrade)
This function is responsible for generating the installation form inputs, such as the destination directory and target database. When upgrading (indicated by the upgrade
hash being non-null) these are fixed and should just be displayed to the user. Otherwise, it must return inputs for selecting them. The functions return value must be HTML for form fields, generated using the ui_table_row
and other ui_
functions.
The example below from WordPress is a good source to copy from, as most PHP scripts that you would want to install will need a target directory and a database. The ui_database_select
function can be used to generate a menu of databases in the domain, with an option to have a new one created automatically just for this script.
sub script_wordpress_params { local ($d, $ver, $upgrade) = @_; local $rv; local $hdir = &public_html_dir($d, 1); if ($upgrade) { # Options are fixed when upgrading local ($dbtype, $dbname) = split(/_/, $upgrade->{'opts'}->{'db'}, 2); $rv .= &ui_table_row("Database for WordPress tables", $dbname); local $dir = $upgrade->{'opts'}->{'dir'}; $dir =~ s/^$d->{'home'}\///; $rv .= &ui_table_row("Install directory", $dir); } else { # Show editable install options local @dbs = &domain_databases($d, [ "mysql" ]); $rv .= &ui_table_row("Database for WordPress tables", &ui_database_select("db", undef, \@dbs, $d, "wordpress")); $rv .= &ui_table_row("Install sub-directory under <tt>$hdir</tt>", &ui_opt_textbox("dir", "wordpress", 30, "At top level")); } return $rv; }
script_scriptname_parse(&domain, version, &in, &upgrade)
This function takes the inputs from the form generated by script_scriptname_params
, parses them an returns an object containing options that will be used when the installation actually happens. If it detects any errors in the input, it should return an error message string instead.
As in the example below, when upgrading the options are almost never changed, so it should return just $upgrade→{'opts'}
, which are the options it was originally installed with. Otherwise, it should look at the hash reference in
which will contain all CGI form variables, and use that to construct a hash of options. The most important keys in the hash are dir
(the installation target directory) and path
(the URL path under the domain's root).
sub script_wordpress_parse { local ($d, $ver, $in, $upgrade) = @_; if ($upgrade) { # Options are always the same return $upgrade->{'opts'}; } else { local $hdir = &public_html_dir($d, 0); $in{'dir_def'} || $in{'dir'} =~ /\S/ && $in{'dir'} !~ /\.\./ || return "Missing or invalid installation directory"; local $dir = $in{'dir_def'} ? $hdir : "$hdir/$in{'dir'}"; local ($newdb) = ($in->{'db'} =~ s/^\*//); return { 'db' => $in->{'db'}, 'newdb' => $newdb, 'multi' => $in->{'multi'}, 'dir' => $dir, 'path' => $in{'dir_def'} ? "/" : "/$in{'dir'}", }; } }
script_scriptname_check(&domain, version, &opts, &upgrade)
This function must verify the installation options in the opts
hash, and return an error message if any are invalid (or undef
if they all look OK). Possible problems include a missing or invalid install directory, a clash with an existing install of the same script in the directory, or a clash of tables in the selected database. As the example below shows, the find_database_table
function provides a convenient way to search for tables by name or regular expression - for most applications, all tables used will be prefixed by a short code, like wp_
in the case of WordPress.
If you are wondering why these checks are not performed in script_scriptname_parse
, the reason is that when a script is installed from the command line, that function is never called. Instead, install options are generated using a different method, and then validated by this function.
sub script_wordpress_check { local ($d, $ver, $opts, $upgrade) = @_; $opts->{'dir'} =~ /^\// || return "Missing or invalid install directory"; $opts->{'db'} || return "Missing database"; if (-r "$opts->{'dir'}/wp-login.php") { return "WordPress appears to be already installed in the selected directory"; } local ($dbtype, $dbname) = split(/_/, $opts->{'db'}, 2); local $clash = &find_database_table($dbtype, $dbname, "wp_.*"); $clash && return "WordPress appears to be already using the selected database (table $clash)"; return undef; }
script_scriptname_files(&domain, version, &opts, &upgrade)
This is the function where the script installer indicates to Virtualmin what files need to be downloaded for the installation to go ahead. Most scripts need only one, which contains the source code - but it is possible to request any number, even zero.
The function must return a list of hash references, each of which should contain the following keys :
-
name
A unique name for this file, used later by thescript_scriptname_install
function. -
file
A short filename for the file, to which it will be saved in/tmp/.webmin
after being downloaded. -
url
The URL that it can be downloaded from. -
nocache
Optional, but can be to 1 to force a download even if the URL is cached by Virtualmin.
In most cases, the ver
parameter is used in the URL and filename to get the correct source archive. WordPress (shown below) is an exception, as it has only a single download URL which always serves up the latest version.
sub script_wordpress_files { local ($d, $ver, $opts, $upgrade) = @_; local @files = ( { 'name' => "source", 'file' => "latest.tar.gz", 'url' => "http://wordpress.org/latest.zip", 'nocache' => 1 } ); return @files; }