{"id":70,"date":"2010-05-29T07:57:24","date_gmt":"2010-05-29T12:57:24","guid":{"rendered":"http:\/\/blogs.wp.stage.cpanel.net\/2010\/05\/frequently_asked_scripts_how_do_i_automate_xyz_after_creating_an_account\/"},"modified":"2010-05-29T07:57:24","modified_gmt":"2010-05-29T12:57:24","slug":"frequently_asked_scripts_how_do_i_automate_xyz_after_creating_an_account","status":"publish","type":"post","link":"https:\/\/devel.www.cpanel.net\/blog\/products\/frequently_asked_scripts_how_do_i_automate_xyz_after_creating_an_account\/","title":{"rendered":"Frequently Asked Scripts: “How do I automate XYZ after creating an account?”"},"content":{"rendered":"

This is a very common question on the cPanel forums. Many times ‘XYZ’ is adding a particular DNS zone or creating a MySQL database. In this blog post, we’ll go through the basics of script hooks and make a post hook that utilizes the XML-API to achieve ‘XYZ.’<\/p>\n

Script hooks<\/a> are files placed into the \/scripts<\/em> directory and named based on the script\u00e2\u20ac™s functionality. Since our question revolves around account creation, the respective script is \/scripts\/wwwacct<\/em> and our script hook file needs to be named postwwwacct<\/em>. If we needed a script to execute before an account is created, we would name the script hook prewwwacct<\/em>. Script hook files are handed the same parameters that are essential to the main script. The postwwwacct<\/em> parameters are detailed here<\/a>.<\/p>\n

We’ll make an example post hook that will add a MySQL database, a database virtual user, and give the new database virtuser access to the new database. There are a few ways you can create these resources, however, using the XML-API<\/a> is arguably the easiest solution. We’ll utilize the XML-API PHP client class<\/a> to send requests to our local server. By using this client class, all the grunt work is handled for us; we just need to provide parameters to the various methods that will create our resources.<\/p>\n

Once you’ve downloaded and unpacked the tarball, you’ll need to place xmlapi.php<\/em> PHP class some place convenient. I placed in a directory in \/home<\/em> called cpanelscripthelpers\/<\/em>.
\n #!\/usr\/bin\/php # Scripts hook to create database and db virtuser; pair the two <?php \/\/set error handling so any warnings are logged ini_set('error_log','\/usr\/local\/cpanel\/logs\/error_log'); ini_set('display_errors',0); \/\/include basic xmlapi client class include('\/home\/cpanelscripthelpers\/xmlapi.php'); <\/code><\/p>\n

We can make our script cleaner and easier to read if we extend the XML-API client class. This also makes it easier to recycle the code later if we were to decide to implement a more robust automation solution for our individual cPanel servers.
\n \/** * extend the basic xmlapi class * add the method for getting args *\/ Class cpScriptsXmlApi extends XMLAPI { } <\/code><\/p>\n

Our postwwwacct<\/em> script is handed all the necessary information through the shell variable $argv<\/em>. So, the first method we need to make in our extended class will parse $argv<\/em> into a PHP array for easy access.
\n public $cliargs = array(); \/** * Simple method to store args into an array * *@params array $argv shell array to be parsed *@return array *\/ public function argv2array ($argv) { $opts = array(); $argv0 = array_shift($argv); while(count($argv)) { $key = array_shift($argv); $value = array_shift($argv); $opts[$key] = $value; } return $opts; } <\/code><\/p>\n

Next, since our extended class is only intended for use in script hooks, we should modify the constructor method to receive $argv<\/em> and parse it.
\n \/** * constructor * *@param array $scriptargs cli $argv that will be parsed *@param string $host *@param string $user *@param string $password *@return cpScriptsXmlApi *\/ public function __construct($scriptargs = array(), $host = null, $user = null, $password = null) { parent::__construct($host,$user,$password); $this->cliargs = $this->argv2array($scriptargs); return $this; } <\/code>
\nWe have three operations that need to be performed after the account is created, so lets make skeleton methods for these operations that we’ll flesh out below.
\n \/** * Create a database *\/ public function createUserDb(){} \/** * Create a db virtuser *\/ public function createDbVirtuser(){} \/** * Assign db privs *\/ public function assignUserPrivs(){} <\/code><\/p>\n

createUserDb()<\/em> is a wrapper for the API1 (link) call adddb (link)<\/em>. adddb<\/em> only requires the name of the new database, along with the normal parameters for any API1 call: user, module, and function.
\n \/** * Create a database * *@param string $user cpanel user to create db as *@param string $dbname name for database *\/ public function createUserDb($user,$dbname){ $args = array($dbname); return $this->api1_query($user,'Mysql','adddb',$args); } <\/code><\/p>\n

createDbVirtuser<\/em> is a wrapper for the API1 call adduser (link)<\/em>. adduser<\/em> will need the new user’s name and password. For convenience, our example is using the same password as the main account. This is ill-advised for production environments. You could easily use a pseudo-random password generator and email the new password it to the account’s address.
\n \/** * Create a db virtuser * *@param string $user cpanel user to create virtuser as *@param string $virtusername name for db virtuser *@param string $password password for new db virtuser *\/ public function createDbVirtuser($user,$virtusername,$password){ $args = array($virtusername,$password); return $this->api1_query($user,'Mysql','adduser',$args); } <\/code><\/p>\n

assignUserPrivs<\/em> is a wrapper for the API1 call adddbuser (link)<\/em>. adddbuser<\/em> needs the database, the user (in our case, the virtuser) and a comma separated list of MySQL privileges to assign. Our method actually takes an array of privileges, instead of a comma separated string, and implodes it.
\n \/** * Assign user privs * *@param string $user cpanel user to work on behalf of *@param string $dbname name of database *@param string $virtusername receiver of privs *@param array $privs array of privileges to assign. *\/ public function assignUserPrivs($user,$dbname,$virtusername,$privs = array()){ $privs = (empty($privs))? array('all'): $privs; \/\/not the best, you can change the default if you wish $priv_str = implode(',',$privs); $args = array($dbname, $virtusername, $priv_str); return $this->api1_query($user,'Mysql','adduserdb',$args); } <\/code><\/p>\n

We now have all of the functionality necessary to perform our post account creation duties. There are just a few more things we need to setup.<\/p>\n

XML-API instantiation:
\n \/\/create our xmlapi object and set it's params $xmlapi = new cpScriptsXmlApi($argv,'127.0.0.1'); <\/code><\/p>\n

Authentication:
\nI like to use a root hash key. If you do not have one, use WHM\u00e2\u20ac™s Setup Remote Access Key<\/em> feature (Main >> Cluster\/Remote Access >> Setup Remote Access Key<\/em>) to generate one. Once you have generated the key, you can find it in \/root\/.accesshash<\/em>.
\n \/\/your root auth hash $hash = file_get_contents('\/root\/.accesshash'); $xmlapi->set_user('root'); $xmlapi->set_hash($hash); <\/code><\/p>\n

XML-API client setup:
\nI like to explicitly set my XML-API client object to use a secure port:
\n $xmlapi->set_port('2087'); \/\/NOTE: you must have compiled OpenSSL or cURL w\/SSL to use secure ports in this class <\/code><\/p>\n

NOTE: All pre-made EasyApache PHP profiles do not have OpenSSL or Curl with SSL enabled by default (at least, as of Easy::Apache v3.2.0 Build 5103 ). If you want to use an SSL port (like in this example), you will need to run EasyApache with the OpenSSL or Curl with SSL option enabled, if you haven’t already done so.<\/p>\n

Our generic database name, virtuser name, and virtuser privileges for the new database.
\n \/\/generic vars for automation\/\/ $dbname = 'adb'; \/\/ \"a database\" $virtusername = 'ausr'; \/\/ \"a user\" $privs = array('all'); \/\/you probably what to look into what you need; 'all' is very liberal <\/code><\/p>\n

So, lets call those methods we made, providing the necessary data. First, we’ll create the database and second, we’ll create the virtuser.
\n $xmlapi->createUserDb($xmlapi->cliargs['user'], $dbname); $xmlapi->createDbVirtuser($xmlapi->cliargs['user'],$virtusername, $xmlapi->cliargs['pass']); \/\/setting passwd same is not wise, but is done for example purposes ONLY <\/code><\/p>\n

Finally, we’ll assign the virtuser to the database. But, before we do that, we need to take a little detour. cPanel 11.25.1 has a feature set called DB Mapping, which is responsible for DB Prefixing. You can find more about DB Mapping on the Integration Blog<\/a> and in forthcoming cPanel documentation at documentation.cpanel.net<\/a>. For now though, we’re just getting a sneak peak and laying the basis for future proof code.<\/p>\n

In cPanel 11.25.0 and earlier, user-created databases are automatically prefixed with the user’s name, followed by an underscore and the database name. Normally, when specifying the database in the API call adduserdb<\/em>, the database must be the full name (both the prefix plus its unique given name, e.g. $username_dbname<\/em>). Notice in method createUserDb<\/em> (API1 call adddb<\/em>), we simply provided a new name. The system will add the prefix if you’re:<\/p>\n