Here is how to run server.sh from PHP

Discussion in 'Developers' Forum' started by zabersoft, Sep 20, 2010.

  1. zabersoft

    zabersoft Member

    Disclaimer: This approach is NOT recommended by the ISPConfig developers - as reiterated by Till in many a post. You should only do this in very specific scenarios. I believe that this approach still has its merits however, so I've decided to share my findings in solving this problem with you all.

    Server.sh is the shell script that runs server.php which updates all the ISPConfig configuration and does all the useful stuff which happens after you create a client, website, ftp user etc. etc.

    Usually server.sh is run by a cron job, as root, every minute

    When to use this approach:
    We decided to manually invoke server.sh after having used the remoting functions in ISPConfig as we had 2 issues we wanted to solve:

    1) Our system setup was time-critical - That is, we wanted ISPConfig to create directories, FTP/DB users immediatly after having called the remoting functions

    2) We wanted to avoid creating an extension, if possible, as we wanted to be able to rapidly deploy our system on new servers - and having to mess about with ISPConfig internals and customizing ISPConfig would slow down our deployment process.

    Potential pitfalls:
    As Till has mentioned then the primary issue with our approach is:
    The recommended approach is to create an extension which gets invoked after the "regular" server.sh cron job finishes. You can read up on this here:

    http://www.howtoforge.com/forums/showthread.php?t=47951

    The solution:
    If you have all the potential pitfalls in mind, and still want to run server.sh manually from apache, then here is how to do it. Basically the solution rests in the fact that you can take advantage of the setuid and setgid bits to run a c executable as root. This has been the major stumbling block in running server.sh from apache - as if it isn't run as root, nothing will work (no directories will be created etc. etc. - as the script doesn't run with the appropriate privileges). So what we do is create a c executable which then in turn invokes server.sh

    First, the PHP code which runs our custom c executable (which we will create a bit later):

    PHP:
    //This function enables us to execute something on the server in the background - grabbed from php.net somewhere
    function execInBackground($cmd) { 
        if (
    substr(php_uname(), 07) == "Windows"){ 
            
    pclose(popen("start /B "$cmd"r"));  
        } 
        else { 
            
    exec($cmd " > /dev/null &");   
        } 


    //Call our "elevated rights" executable and tell it to run server.sh
    execInBackground("/path/to/your/executable/elevaterights updateispcon");

    //Let's wait for it to finish before moving on to other things - like copying files or whatever you wanna do
    while (!is_dir($document_root)) {
      
    //foo
    }
    I tried to simplify things a bit here - There is actually no need to execute the elevaterights executable in the background - you can easily just do a regular exec without piping output to dev null

    Anyhoo - now the critical part: The c executable where all the magic happens

    Code:
    /*
    compile using: gcc elevaterights.c -o elevaterights 
    then: chown root:root elevaterights 
    next: chmod 6755 elevaterights - this enables the system to run this executable as root (the 4 sets the setuid bit, 2 sets the setgid bit - 4+2 = 6(755)) 
    */
    #include <stdio.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <strings.h>
    
    int main( int argc, char *argv[] )
    {
       setuid( 0 );
       setgid( 0 );
       
    //Run the ISPConfig updater script
     if (strcmp(argv[1],"updateispcon") == 0) {
         puts("Executing ISPConfig Update script ...");
         system( "/usr/local/ispconfig/server/server.sh" );
    	 system( "cp /path/to/your/executable/lockfile /path/to/your/executable/.sitekick_lock" );
     } else  if (strcmp(argv[1],"restarthttpd") == 0) {
      //lock file is created in PHP
      FILE *fp = fopen("/path/to/your/executable/.sitekick_lock","r");
    	if( fp ) {
    	// exists
    	fclose(fp);
         puts("Restarting Apache ...");
         system( "/etc/init.d/apache2 restart" );
    	 puts("Deleting restart lock file ...");
    	 system( "rm /path/to/your/executable/.sitekick_lock" );
       } else {
        puts("No lock file found - doing nothing ...");
       }
        return 0;
    }
    
    } //end main
    
    To explain a bit ... The reason you see all that mumbo jumbo regarding a lockfile and not just a straightforward execution of server.sh is that when we were going through this process we ran into a problem with server.sh restarting apache. Usually when ISPConfig adds new vhosts it restarts apache so they are up and running immediatly. For some reason, when we were invoking server.sh from apache this caused the process to hang. I suspect that this is a recursiveness thing - as I could not track down any actual error conditions. That is - calling server.sh from apache which then tries to restart apache hangs, as it is trying to kill itself. So to speak. That's my guess in anyway.

    So, we solved this by creating a new cronjob which is run every minute and invoked like so:

    /path/to/your/executable/elevaterights restarthttpd

    The reference you see to "lockfile" in the above code is just a dummy file which gets copied to .sitekick_lock - which the cron job then looks for to determine whether apache needs a restart.

    In addition to this, we had to tweak a bit in the ISPConfig code to stop it from restarting apache when it has done its job. We just added a single line (return 0; ) to the function restartHttpd in server/mods-available/web_module.inc.php (around line 130)

    I would be very interested to see if somebody who had another setup were to try the above code - and just removed all the lockfile / manual restart whatnot and see if the same thing happens for them. Because - well as this approach works then the whole apache restart issue makes it slightly messy - and some of the benefit of this approach - which is not altering any ISPConfig files gets slightly lost as we need to do that tweak to web_module.inc.php

    Anyhoo - That's my story - Hope this helps someone, and maybe this can spark a discussion on how silly I am in doing all this and that there is a much better approach available (Here's hoping - haha)
     
    Last edited: Sep 20, 2010
  2. till

    till Super Moderator Staff Member ISPConfig Developer

    If I understand your explanation correctly, you have to restart apache with a one minute cronjob. So your changes get applied after about one minute. Thats the same time that the normal ispconfig system takes to apply the changes, so I dont see the real benefit in the time to get changes applied.

    If you want to get changes applied faster, then the approach that was used in ispconfig 2 might be a good choice.

    1) Comment out the server.sh root cronjob.
    2) create a bash script, e.g. /usr/local/bin/ispconfig_server.sh with the following content:

    Code:
    #!/bin/bash
    
    export PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/X11R6/bin
    
    . /etc/profile
    
    error ()
    {
      echo "ERROR: $1"
      exit 1
    }
    
    ID="id -u"
    MYUID=`$ID 2>/dev/null`
    
    if [ ! -z "$MYUID" ];
    then
      if [ $MYUID != 0 ];
      then
        error "You need root privileges to run this script! / Vous devez avoir les privileges root pour executer ce script!";
      fi
    fi
    
    while (true) do
      if [ -f /usr/local/ispconfig/interface/web/temp/.run ]; then
        touch /usr/local/ispconfig/server/temp/.run2
        rm -f /usr/local/ispconfig/interface/web/temp/.run
        /usr/bin/php -q /usr/local/ispconfig/server/server.php
        rm -f /usr/local/ispconfig/server/temp/.run2
      fi
      sleep 10
    done
    
    exit 0
    
    Start this shell script in the background, it will run forever until you kill it or you reboot the server. It has a very low resource usage. The script checks every 10 seconds if a file /usr/local/ispconfig/interface/web/temp/.run exists and if that file exists it will start the ispconfig update process. You can even reduce the time to less then 10 seconds if nescessary.

    So if you want to start an update from the ispconfig interface then, you just execute this php command:

    touch('/usr/local/ispconfig/interface/web/temp/.run');

    This variant is only usable on single server systems, thats why we dont use it in ispconfig 3.
     
    Last edited: Sep 20, 2010
  3. zabersoft

    zabersoft Member

    Excellent

    Hi Till,

    Thank you for that nice reply and an excellent new approach - I will certainly give it a go as this would probably be a lot cleaner than what I am doing now.

    However, one question - The .run file which tells the bash script (ispconfig_server.sh) to invoke server.php - this gets created by ISPConfig whenever an update to its config has been made? You didn't remove this in ISPConfig 3 even though this approach has been deprecated?

    Just making sure :)

    Thanks again!

    PS: The reason for all this was that it was critical that directories etc. got created ASAP so we could set up our system files immediatly and update databases etc. - That is, a complete setup of the systems. Accessibility to the site was of a secondary concern - so it was OK that apache got restarted a minute later - as at that point everything was ready to go. With the other approach, the user risked having to wait up to 2 minutes+ for everything to finish (And we don't like to keep people waiting, looking at a spinner ;) ) - Now everything is all set up in 30 seconds or less, typically.

    With your new/old approach then +10 seconds is AOK by us, and should solve our problems :)
     
    Last edited: Sep 20, 2010
  4. till

    till Super Moderator Staff Member ISPConfig Developer

    No, the file does not get created by ispconfig. You can either invoke it the same way that you do it for your approach or you add the line at the end of the remoting index.php file.
     
  5. zabersoft

    zabersoft Member

    I have now spent the day implementing Till's approach and it works like a beaut.

    What I've done is create a cron job which runs a small PHP file which checks whether the bash script Till posted is running, and if not starts it. This is to ensure that it always is active (even after a reboot) - I guess you could add it to /etc/init.d but I didn't want to bother with that.

    This is my PHP cron job:

    PHP:
    //Path to sitekick internal system files
    $sitekick_sys "/var/www/xxxxx/sitekick/"

         
    //Check if ISPConfig updater process is running - if not, start it
          
    exec("ps aux | grep '/var/www/xxxxx/sitekick/[i]spconfig_updater.sh' | wc -l"$check);
          if(
    intval($check[0]) > 0) { 
              echo(
    'ISPConfig updater is already running');
          } else {
            echo(
    'Updater not running - starting in background');
            
    $command "nohup /var/www/xxxxx/sitekick/ispconfig_updater.sh > /dev/null &";
            
    exec($command);
            echo 
    $command;
          }
    As you can see I placed the bash script in another location than the one Till mentioned, as I want to keep our own stuff outside of ISPConfig dirs. Also, I had to change ownership of the script to root for everything to work properly.

    I did not disable the "regular" server.sh cron job, as I saw no reason to. I still want things to work as usual in ISPConfig if we set up a client/vhost manually in the ISPConfig back-end.

    All Till's bash script really does is just wait for .run to be touched/created and then do the update. I modded it slightly so that it only waits 5 seconds between each check. I can't see that this tweak should have any detrimental effect.

    I still use my elevated rights executable as posted in the original post - but now its job has changed to:
    1) touch .run (you need to be root to do this, so it's a problem from apache)
    2) Run all my post ISPConfig update actions as root - I changed this to all be located in a custom PHP file which is run by my elevated rights executable - for easier maintenance

    Anyway - Here's a happy man signing off as everything is working as it should :)
     

Share This Page