Child pages
  • Tutorial - Create a Standardized Hook

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Introduction

Excerpt
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 system to preserve your customizations.

To solve this problem, this tutorial creates the following hooks:

  • pre-stage hook that runs immediately before the upcp script to copy the customized Horde configuration file (/usr/local/cpanel/base/horde/turba/config/backends.php) to the backends.php.temp file.
  • post-stage hook that runs immediately after the upcp script to copy the backends.php.temp file back into the backends.php file, and then deletes the backends.php.temp file.
Note
titleNote:

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 documentation.

Create and register hooks


Section


Column
width72px


Column

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.


 


 

Section


Column
width72px


Column

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 documentation.

Code Block
languageperl
firstline3
linenumberstrue
# Package this module.
package MyHook; 



 


 

Section


Column
width72px


Column

Set the strict pragma and use warnings.

These declarations instruct Perl to return errors if the file contains potentially-unsafe code.

Note
titleNote:

You can omit the warnings declaration in production code, but we strongly recommend that you use it during development. 


Code Block
languageperl
firstline6
linenumberstrue
# Return errors if Perl experiences problems.
use strict;
use warnings;



 


 

Section


Column
width72px


Column

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.
Code Block
languageperl
firstline10
linenumberstrue
# 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;



 


 

Section


Column
width72px


Column

Instantiate a new Cpanel::Logger object.

Use the Cpanel::Logger module's new() method to instantiate a new logging object.

Code Block
languageperl
firstline19
linenumberstrue
# Instantiate the cPanel logging object.
my $logger = Cpanel::Logger->new();


Note
titleNote:

By default, the new() method creates an object that logs to the usr/local/cpanel/logs/error_log file.  



 


 

Section


Column
width72px


Column

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 documentation.

Code Block
languageperl
firstline22
linenumberstrue
# 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;
}



 


 

Section


Column
width72px


Column

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.

Code Block
languageperl
firstline42
linenumberstrue
# Set the file to preserve and the temp file location.
my $preserve = "/usr/local/cpanel/base/horde/turba/config/backends.php";
my $temp = "/usr/local/cpanel/base/horde/turba/config/backends.php.temp";


Note
titleNote:

The temporary file's location is arbitrary. 



 


 

Section


Column
width72px


Column

Create the pre hook's subroutine.

The system will run the copyfile subroutine immediately before the upcp process.

  • Line 49 reads the entire @_ stream until end-of-line (EOL).

    Warning
    titleImportant:
    • 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.


  • Line 52 adds a logging action to the subroutine. This step is optional, but we strongly recommend that you include it for debugging purposes.
  • Line 55 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.

      Warning
      titleWarning:

      Failure does not block the upcp event. 


Code Block
languageperl
firstline46
linenumberstrue
# 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: $!";
};


Note
titleNote:

Make certain that you use the same subroutine names that you specified in the describe() method in step 6.



 


 

Section


Column
width72px


Column

Create the post hook's subroutine.

The system will run the replacefile subroutine immediately after the upcp process.

  • Line 61 reads the entire @_ stream until end-of-line (EOL).

    Warning
    titleImportant:
    • 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.


  • Lines 64 and 70 add logging actions to the subroutine. This step is optional, but we strongly recommend that you include it for debugging purposes.
  • Line 67 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.

  • Line 73 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.

Code Block
languageperl
firstline58
linenumberstrue
# 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: $!";
};


Note
titleNote:

Make certain that you use the same subroutine names that you specified in the describe() method in step 6.



 


 

Section


Column
width72px


Column

Save the file.

Save your hook action code to one of the following locations on your cPanel & WHM server:

  • /usr/local/cpanel 
  • /usr/local/cpanel/3rdparty/perl/514/lib64/perl5/cpanel_lib/x86_64-linux-64int
  • /usr/local/cpanel/3rdparty/perl/514/lib64/perl5/cpanel_lib
  • /usr/local/cpanel/3rdparty/perl/514/lib64/perl5/5.14.4/x86_64-linux-64int
  • /usr/local/cpanel/3rdparty/perl/514/lib64/perl5/5.14.4
  • /opt/cpanel/perl5/514/site_lib/x86_64-linux-64int
  • /opt/cpanel/perl5/514/site_lib
  • /var/cpanel/perl5/lib
  • /var/cpanel/perl5/lib
Warning
titleImportant:

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:

Expand
titleClick to view complete Perl code...


Code Block
languageperl
linenumberstrue
#!/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/horde/turba/config/backends.php";
my $temp = "/usr/local/cpanel/base/horde/turba/config/backends.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; 




 


 

Section


Column
width72px


Column

Register your hooks.

Run the following command as the root user to register the two new hooks:

Code Block
languagebash
/usr/local/cpanel/bin/manage_hooks add module MyHook 

If the system registered the hooks correctly, the script produces the following output:

Code Block
languagebash
linenumberstrue
Added hook for System::upcp to hooks registry
Added hook for System::upcp to hooks registry 


Note
titleNote:

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 documentation.



 


 

Section


Column
width72px


Column

Test your hooks.

To test upcp hooks, click Click to Upgrade in WHM's Upgrade to Latest Version interface (WHM >> Home >> cPanel >> Upgrade to Latest Version) or run the following command:

Code Block
languagebash
/usr/local/cpanel/scripts/upcp

After the upcp process finishes, the /usr/local/cpanel/logs/error_log file should contain the following entries:

Code Block
languagetext
linenumberstrue
[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.


 


 

Section


Column
width72px


Column

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:

Code Block
languagebash
/usr/local/cpanel/bin/manage_hooks list 

If the hooks registered successfully, the output should resemble the following example:

Code Block
languagetext
linenumberstrue
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:

    Code Block
    languageperl
    linenumberstrue
    # 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:

    Code Block
    languageperl
    _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:

Code Block
languagetext
linenumberstrue
[2015-06-16 11:08:29 -0500] info [hook] {
   "stage" : "pre",
   "point" : "main",
   "category" : "System",
   "event" : "upcp"
}