How to Run a Shell Script as Daemon on CentOS: Comprehensive Guide for RhodeCode Deployment


6 views

When deploying RhodeCode on CentOS systems, one common hurdle is the absence of native init scripts for RedHat-based distributions. Unlike Debian or Gentoo, CentOS requires custom daemon management solutions. The core problem involves:

  • Persistent process execution
  • Proper user permissions
  • Log management
  • PID file handling

For a robust daemon implementation, we need these key elements:

# Directory structure essentials
/var/run/rhodecode/    # For PID files
/var/www/rhodecode/    # Configuration and logs
/srv/rhodecode/        # Execution scripts

Here's an improved version of start.sh with better error handling:

#!/bin/bash
# RhodeCode execution wrapper

WDIR=/var/www/rhodecode
VIRTUALENV_DIR=/opt/python_virtualenvironments/rhodecode-venv
export PYTHON_EGG_CACHE=/tmp/.python-eggs

# Environment validation
[ ! -d "$VIRTUALENV_DIR" ] && { echo "Virtualenv missing"; exit 1; }

source $VIRTUALENV_DIR/bin/activate || exit 1

cd $WDIR || exit 1
exec paster serve production.ini >> debug.log 2>&1

A more resilient init.d script with proper status checks:

#!/bin/sh
# RhodeCode System V init script

NAME=rhodecode-server
DESC="RhodeCode Version Control"
USER=rhodecode
PID_FILE=/var/run/rhodecode/pid
CMD="/srv/rhodecode/start.sh"
LOCK_FILE=/var/lock/subsys/$NAME

. /etc/init.d/functions

validate_environment() {
    [ -f "$CMD" ] || return 1
    [ -d "$(dirname $PID_FILE)" ] || return 1
    id $USER >/dev/null 2>&1 || return 1
    return 0
}

start() {
    validate_environment || {
        echo "Environment validation failed"
        return 1
    }
    
    echo -n $"Starting $DESC: "
    daemon --user=$USER --pidfile=$PID_FILE "$CMD &"
    RETVAL=$?
    [ $RETVAL -eq 0 ] && touch $LOCK_FILE
    echo
    return $RETVAL
}

stop() {
    echo -n $"Stopping $DESC: "
    killproc -p $PID_FILE
    RETVAL=$?
    [ $RETVAL -eq 0 ] && rm -f $LOCK_FILE $PID_FILE
    echo
    return $RETVAL
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        sleep 2
        start
        ;;
    *)
        echo $"Usage: $0 {start|stop|restart}"
        exit 1
esac

exit $?

When encountering hangs during startup:

  1. Verify the process isn't already running: ps aux | grep paster
  2. Check SELinux context: ls -Z /srv/rhodecode/start.sh
  3. Test direct execution: sudo -u rhodecode /srv/rhodecode/start.sh

For modern CentOS versions (7+), consider this systemd unit file:

[Unit]
Description=RhodeCode Version Control
After=network.target

[Service]
User=rhodecode
Group=rhodecode
WorkingDirectory=/var/www/rhodecode
Environment=PYTHON_EGG_CACHE=/tmp/.python-eggs
ExecStart=/opt/python_virtualenvironments/rhodecode-venv/bin/paster serve production.ini
Restart=always
PIDFile=/var/run/rhodecode/pid

[Install]
WantedBy=multi-user.target

After implementing either solution:

# Set proper permissions
chmod 755 /srv/rhodecode/start.sh
chmod 755 /etc/init.d/rhodecode-server

# Enable auto-start
chkconfig --add rhodecode-server
chkconfig rhodecode-server on

When converting a shell script into a proper daemon on CentOS, several critical components must be addressed:

# Essential daemon characteristics:
1. Background execution (detaching from terminal)
2. PID file management
3. Proper user context
4. Logging infrastructure
5. System V init script compliance

The current setup shows several common pitfalls in daemon implementation:

# Problem areas in the original script:
1. Missing proper daemonization in start.sh
2. Incomplete PID file handling
3. No proper process detachment
4. Log rotation not configured

Here's the corrected version of the start.sh script with proper daemonization:

#!/bin/bash
WDIR=/var/www/rhodecode
VIRTUALENV_DIR=/opt/python_virtualenvironments/rhodecode-venv
PID_FILE=/var/run/rhodecode/pid
LOG_DIR=/var/log/rhodecode

# Ensure proper environment
export PYTHON_EGG_CACHE=/tmp/.python-eggs
source $VIRTUALENV_DIR/bin/activate

# Daemonization function
daemonize() {
    # Double-fork to properly daemonize
    (
        cd $WDIR
        exec /usr/bin/paster serve production.ini \
            >> $LOG_DIR/debug.log \
            2>> $LOG_DIR/error.log
    ) & echo $! > $PID_FILE
}

case "$1" in
    start)
        daemonize
        ;;
    stop)
        [ -f $PID_FILE ] && kill $(cat $PID_FILE)
        ;;
    *)
        echo "Usage: $0 {start|stop}"
        exit 1
        ;;
esac

The improved /etc/init.d/rhodecode-server should handle process management more robustly:

#!/bin/sh
#
# chkconfig: 345 90 10
# description: RhodeCode version control server

### BEGIN INIT INFO
# Provides: rhodecode-server
# Required-Start: $network $local_fs $remote_fs
# Required-Stop: $network $local_fs $remote_fs
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Short-Description: RhodeCode server
### END INIT INFO

NAME=rhodecode-server
USER=rhodecode
SCRIPT=/srv/rhodecode/start.sh
PID_FILE=/var/run/rhodecode/pid
LOCK_FILE=/var/lock/subsys/$NAME

. /etc/init.d/functions

start() {
    echo -n $"Starting $NAME: "
    daemon --user=$USER --pidfile=$PID_FILE $SCRIPT start
    RETVAL=$?
    [ $RETVAL -eq 0 ] && touch $LOCK_FILE
    echo
    return $RETVAL
}

stop() {
    echo -n $"Stopping $NAME: "
    killproc -p $PID_FILE $SCRIPT
    RETVAL=$?
    [ $RETVAL -eq 0 ] && rm -f $LOCK_FILE
    echo
    return $RETVAL
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        start
        ;;
    status)
        status -p $PID_FILE $NAME
        RETVAL=$?
        ;;
    *)
        echo $"Usage: $0 {start|stop|restart|status}"
        RETVAL=2
        ;;
esac
exit $RETVAL

Create /etc/logrotate.d/rhodecode for proper log management:

/var/log/rhodecode/*.log {
    weekly
    missingok
    rotate 12
    compress
    delaycompress
    notifempty
    create 640 rhodecode rhodecode
    sharedscripts
    postrotate
        /etc/init.d/rhodecode-server restart > /dev/null
    endscript
}

Complete the setup with these commands:

# Create necessary directories
sudo mkdir -p /var/log/rhodecode
sudo chown rhodecode:rhodecode /var/log/rhodecode

# Set up init script
sudo chmod 755 /etc/init.d/rhodecode-server
sudo chkconfig --add rhodecode-server
sudo chkconfig rhodecode-server on

# First start
sudo service rhodecode-server start