[Development Guides Home](/guides) >> [Quickstart Development Guide](/guides/quickstart-development-guide) # Tutorial - Create a Standardized Hook ## Introduction This tutorial adds custom hooks to preserve a customized configuration file. The cPanel & WHM update process (`upcp`) sometimes overwrites customized files with the cPanel-provided default versions. If you experience this problem, use our [Standardized Hooks](/guides/guide-to-standardized-hooks) system to preserve your customizations. To solve this problem, this tutorial creates the following hooks: * A `pre`-stage hook that runs immediately before the `upcp` script to copy a customized Roundcube configuration file from `/usr/local/cpanel/base/3rdparty/roundcube/config/config.inc.php` to the `config.inc.php.temp` file. * A `post`-stage hook that runs immediately after the `upcp` script to copy the `config.inc.php.temp` file back into the `config.inc.php` file, and then deletes the `config.inc.php.temp` file. This tutorial creates a Perl module, but you can also write hook action code in PHP or any other preferred programming language. For more information, read our [Hook Action Code](/guides/guide-to-standardized-hooks/guide-to-standardized-hooks-hook-action-code) documentation. ## Create a file to contain your hook action code Create a new Perl file to contain your hook action code. * The filename that you use **must** meet the internal Perl interpreter's requirements for module names. * This tutorial uses the `MyHook.pm` file. ## Package the module This declaration instructs Perl to treat all of the file's functions as part of the `MyHook` module. For more information, read [perldoc.perl.org's package](https://perldoc.perl.org/functions/package) documentation. ```undefined perl # Package this module package MyHook; ``` ## Set the strict pragma and use warnings These declarations instruct Perl to return errors if the file contains potentially-unsafe code. You can omit the warnings declaration in production code, but we **strongly** recommend that you use it during development. ```undefined perl # Return errors if Perl experiences problems. use strict; use warnings; ``` ## Use additional modules These declarations instruct Perl to use the following additional modules: * `Cpanel::Logger` — This module uses cPanel & WHM's logging functionality to write messages to log files. * `File::Copy` — This core Perl module includes methods to manipulate files. * `JSON` — This module properly encodes and decodes JSON. ```undefined perl # Use cPanel's error logging module. use Cpanel::Logger; # Use the core Perl module with file-copying functionality. use File::Copy; # Properly decode JSON. use JSON; ``` ## Instantiate a new Cpanel::Logger object Use the `Cpanel::Logger` module's `new()` method to instantiate a new logging object. ```undefined perl # Instantiate the cPanel logging object. my $logger = Cpanel::Logger->new(); ``` By default, the `new()` method creates an object that logs to the `/usr/local/cpanel/logs/error_log` file. * You can also specify a custom log location. ## Create a describe() method to embed hook attributes in your code Create a `describe()` method that contains the attributes for all of the hooks in the code. This step is optional, but it allows you to simplify the way in which you call the `/usr/local/cpanel/bin/manage_hooks` utility when you register the hooks. For this tutorial, the `describe()` method includes the attributes for the following hooks: * The first hook executes the `MyHook` module's `copyfile` subroutine during the `pre` stage of the System category's `upcp` event. * The second hook executes the `MyHook` module's `replacefile` subroutine during the `post` stage of the System category's `upcp` event. For more information, read our [Guide to Standardized Hooks - The manage_hooks Utility](/guides/guide-to-standardized-hooks/guide-to-standardized-hooks-the-manage-hooks-utility) documentation. ```undefined perl # Embed hook attributes alongside the action code. sub describe { my $hooks = [ { 'category' => 'System', 'event' => 'upcp', 'stage' => 'pre', 'hook' => 'MyHook::copyfile', 'exectype' => 'module', },{ 'category' => 'System', 'event' => 'upcp', 'stage' => 'post', 'hook' => 'MyHook::replacefile', 'exectype' => 'module', } ]; return $hooks; } ``` ## Specify the files to manipulate Create variables that contain the absolute paths for the configuration file that you wish to preserve and the temporary file that you wish to create in order to preserve it. The temporary file's location is arbitrary. ```undefined perl # Set the file to preserve and the temp file location. my $preserve = "/usr/local/cpanel/base/3rdparty/roundcube/config/config.inc.php"; my $temp = "/usr/local/cpanel/base/3rdparty/roundcube/config/config.inc.php.temp"; ``` ## Create the pre hook's subroutine The system will run the `copyfile` subroutine immediately before the `upcp` process. The following example: * Reads the entire `@_` stream until end-of-line (EOL). * Every hook subroutine **must** perform this action. * Because this code uses the JSON module, it treats this data as a JSON-encoded data structure. After the system decodes the JSON string, the native data structure becomes a hash. * Adds a logging action to the subroutine. This step is optional, but we **strongly** recommend that you include it for debugging purposes. *Uses the `File::Copy` module's `copy()` function to copy the contents of the `$preserve` file into the `$temp` file. * If the `$temp` file does not already exist, the system will automatically create it. * If the copy fails, the system will log a failure message that includes any error messages. Failure does **not** block the `upcp` event. ```undefined perl # Before upcp, copy the file to the temp file. sub copyfile { # Get the data that the system passes to the hook. my ( $context, $data ) = @_; # Add a log entry for debugging. $logger->info("***** Copy my file before upcp *****"); # Copy my file. copy($preserve,$temp) or die "Copy failed: $!"; }; ``` Make **certain** that you use the same subroutine names that you specified in the [create a `describe()` method](#create-a-describe-method-to-embed-hook-attributes-in-your-code) step. ## Create the post hook's subroutine The system will run the `replacefile` subroutine immediately after the `upcp` process. The following example: * Reads the entire `@_` stream until end-of-line (EOL). * Every hook subroutine **must** perform this action. * Because this code uses the JSON module, it treats this data as a JSON-encoded data structure. After the system decodes the JSON string, the native data structure becomes a hash. * Adds logging actions to the subroutine. This step is optional, but we **strongly** recommend that you include it for debugging purposes. * Uses the `File::Copy` module's `copy()` function to copy the contents of the `$temp` file back into the `$preserve` file. * If the `$preserve` file no longer exists, the system will automatically create it. * If the copy fails, the system will log a failure message that includes any error messages. * Deletes the `$temp` file. * This step is optional, but ensures that your system does not retain unnecessary files. * If the deletion fails, the system will log a failure message that includes any error messages. ```undefined perl # After upcp, copy the file back into place. sub replacefile { # Get the data that the system passes to the hook. my ( $context, $data ) = @_; # Add a log entry for debugging. $logger->info("***** Replace the temp file *****"); # Replace my file. copy($temp,$preserve) or die "Replacement failed: $!"; # Add a log entry for debugging. $logger->info("***** Delete the temp file *****"); # Delete the temp file to keep cruft off my system. unlink($temp) or die "Cruft removal failed: $!"; }; ``` Make **certain** that you use the same subroutine names that you specified in the [create a `describe()` method](#create-a-describe-method-to-embed-hook-attributes-in-your-code) step. ## Save the file Save your hook action code to one of the following locations on your cPanel & WHM server: * `/opt/cpanel/perl5/514/site_lib/x86_64-linux-64int` * `/opt/cpanel/perl5/514/site_lib` * `/var/cpanel/perl5/lib` You **must** save hook action code in one of these locations, or you will receive an `@INC` error when you attempt to register the hook. Your final file should appear similar to the following example: ```undefined perl #!/usr/bin/perl # Package this module. package MyHook; # Return errors if Perl experiences problems. use strict; use warnings; # Use cPanel's error logging module. use Cpanel::Logger; # Use the core Perl module with file-copying functionality. use File::Copy; # Properly decode JSON. use JSON; # Instantiate the cPanel logging object. my $logger = Cpanel::Logger->new(); # Embed hook attributes alongside the action code. sub describe { my $hooks = [ { 'category' => 'System', 'event' => 'upcp', 'stage' => 'pre', 'hook' => 'MyHook::copyfile', 'exectype' => 'module', },{ 'category' => 'System', 'event' => 'upcp', 'stage' => 'post', 'hook' => 'MyHook::replacefile', 'exectype' => 'module', } ]; return $hooks; } # Set the file to preserve and the temp file location. my $preserve = "/usr/local/cpanel/base/3rdparty/roundcube/config/config.inc.php"; my $temp = "/usr/local/cpanel/base/3rdparty/roundcube/config/config.inc.php.temp"; # Before upcp, copy the file to the temp file. sub copyfile { # Get the data that the system passes to the hook. my ( $context, $data ) = @_; # Add a log entry for debugging. $logger->info("***** Copy my file before upcp *****"); # Copy my file. copy($preserve,$temp) or die "Copy failed: $!"; }; # After upcp, copy the file back into place. sub replacefile { # Get the data that the system passes to the hook. my ( $context, $data ) = @_; # Add a log entry for debugging. $logger->info("***** Replace the temp file *****"); # Replace my file. copy($temp,$preserve) or die "Replacement failed: $!"; # Add a log entry for debugging. $logger->info("***** Delete the temp file *****"); # Delete the temp file to keep cruft off my system. unlink($temp) or die "Cruft removal failed: $!"; }; 1; ``` ## Register your hooks Run the following command as the `root` user to register the two new hooks: ``` /usr/local/cpanel/bin/manage_hooks add module MyHook ``` If the system registered the hooks correctly, the script produces the following output: ``` Added hook for System::upcp to hooks registry Added hook for System::upcp to hooks registry ``` If you do not include the `describe()` method in your custom code, you **must** include additional information when you run this script. For more information, read our [Guide to Standardized Hooks - The manage_hooks Utility](/guides/guide-to-standardized-hooks/guide-to-standardized-hooks-the-manage-hooks-utility) documentation. ## Test your hooks To test `upcp` hooks, click *Click to Upgrade* in WHM's [*Upgrade to Latest Version*](https://docs.cpanel.net/whm/cpanel/upgrade-to-latest-version/) interface (*WHM >> Home >> cPanel >> Upgrade to Latest Version*) or run the following command: ``` /usr/local/cpanel/scripts/upcp ``` After the `upcp` process finishes, the `/usr/local/cpanel/logs/error_log` file should contain the following entries: ``` [2015-06-16 11:08:29 -0500] info [hook] ***** Copy my file before upcp ***** [2015-06-16 11:08:29 -0500] info [hook] ***** Replace the temp file ***** [2015-06-16 11:08:29 -0500] info [hook] ***** Delete the temp file ***** ``` If some or all of these entries do not appear in the error log, or if you see error messages in the same portion of the file, you may need to troubleshoot issues on your system. ## Troubleshooting If you experience problems with hooks, the following troubleshooting methods may help you to find and fix the issue: ### Check whether your hooks registered properly To view a list of registered hooks, run the following command: ``` /usr/local/cpanel/bin/manage_hooks list ``` If the hooks registered successfully, the output should resemble the following example: ``` System: upcp: stage: pre weight: 100 id: lb6vAuWAZ9JNmzIIEsiLAeJw exectype: module hook: MyHook::copyfile -- stage: post weight: 200 id: NhhoUZ1GnGSfpi3dpcClKrnT exectype: module hook: MyHook::replacefile ``` ### Add more logging to your hook action code To cause your hook action code to log additional information, perform the following steps: 1. Add the following subroutine to your hook action code: ``` # Log extra info for debugging. sub _more_logging { my ( $context, $data ) = @_; my $pretty_json = JSON->new->pretty; $logger->info( $pretty_json->encode($context) ); $logger->info( $pretty_json->encode($data) ); } ``` 2. Add the following line of code to any points in your code that you believe may cause the problems: ``` _more_logging( $context, $data ); ``` This subroutine causes the function to log additional information about the hook event to the error log file, which appears similar to the following example: ``` [2015-06-16 11:08:29 -0500] info [hook] { "stage" : "pre", "point" : "main", "category" : "System", "event" : "upcp" } ```