Modern (0.10.x+) NodeJS RPMs on CentOS/REHL 5 and 6

I posted back in January about RPM Spec Files for nodejs 0.9.5 and v8 on CentOS 6. In that post I also said that I was unable to get recent NodeJS to build on CentOS 5 because of a long chain of dependencies including node-gyp, v8, http-parser, glibc, etc. I said I couldn’t get it to build. Well, I have good news for both distro versions.

On the CentOS/RHEL 6 side, thanks to a lot of work by T. C. Hollingsworth and others, NodeJS 0.10.5 is currently in the official EPEL repositories. They seem to be keeping the packages pretty current, but if you need newer, you can always grab the SRPMs from EPEL and build the newer versions. This is great, because it means I no longer need to maintain the spec files and do my own builds. I don’t think I really did anything to help get this package in EPEL, other than ping a few people and comment on a few tickets.

For CentOS/RHEL 5, I finally have packages, but they’re not exactly pretty. The dependency solving issues still stand; they’re rooted at the dependency of node-gyp which requires the v8 C++ JavaScript library, and is required to compile shared object addons. The best solution that I (and a few others) could find is simply not to build node-gyp, and not to have support for addons or package any addons; we just have the binaries that NodeJS’s Makefile creates, and everything else is interpreted. A coworker found https://github.com/kazuhisya/nodejs-rpm which contains a configure patch and specfile for a dead-simple CentOS 5/6 RPM of NodeJS 0.10.9, which essentially just uses EPEL’s python26 packages to power the NodeJS build process, configures and uses the Makefile’s make binary command to spit out a NodeJS binary tarball, and then packages that. That whole process way out of line from the Fedora Packaging Guidelines, and also only dumps out nodejs, nodejs-binary and nodejs-debuginfo packages, so I also can’t just substitute in a different package name in my puppet manifests (which install nodejs, nodejs-devel and npm packages). So I forked that repository and made some changes to the specfile: I gave the package name a prefix (“cmgd_”, since that’s where I work these days) and some warnings in the description, to make it abundantly clear that these packages are very far from what you find in EPEL and other repositories, and broke npm and the devel files out into their own subpackages. Hopefully this spec file will be of use to someone else who also has the unfortunate need of supporting recent NodeJS on CentOS 5. If there’s enough interest, I’ll consider building the packages and putting them in a repository somewhere.

You can see the NodeJS 0.10.9 on CentOS 5 spec file, a patch, and the READMEs at https://github.com/jantman/nodejs-rpm-centos5. Patches and/or pull requests are greatly appreciated, especially from anyone who wants to make the spec file more Fedora guidelines compliant.

RPM Spec Files for nodejs 0.9.5 and v8 on CentOS 6

The latest version of nodejs that I could find as an RPM for CentOS was 0.6.16, from http://patches.fedorapeople.org/oldnode/stable/. That’s the one that puppetlabs currently uses in their puppetlabs-nodejs module. There is, however, a nodejs 0.9.5 RPM in the Fedora Rawhide (19) repository. Below are some patches to that specfile, and the specfile for its v8 dependency, to get them to build on CentOS 6. You can also find the full specfiles on my github specfile repository. I had originally wanted to get them built on CentOS 5 as well, but after following the dependency tree from nodejs to http-parser to gyp, and then finding issues in the gyp source that are incompatible with CentOS 5′s python 2.4, I gave up on that target.

nodejs.spec, diff from Fedora Rawhide nodejs-0.9.5-9.fc18.src.rpm, buildID=377755 (full specfile)

diff --git a/nodejs.spec b/nodejs.spec
index 050ed86..86c0f4b 100644
--- a/nodejs.spec
+++ b/nodejs.spec
@@ -1,6 +1,6 @@
 Name: nodejs
 Version: 0.9.5
-Release: 9%{?dist}
+Release: 10%{?dist}
 Summary: JavaScript runtime
 License: MIT and ASL 2.0 and ISC and BSD
 Group: Development/Languages
@@ -25,7 +25,7 @@ Source6: nodejs-fixdep
 BuildRequires: v8-devel >= %{v8_ge}
 BuildRequires: http-parser-devel >= 2.0
 BuildRequires: libuv-devel
-BuildRequires: c-ares-devel
+BuildRequires: c-ares-devel >= 1.9.0
 BuildRequires: zlib-devel
 # Node.js requires some features from openssl 1.0.1 for SPDY support
 BuildRequires: openssl-devel >= 1:1.0.1
@@ -165,9 +165,13 @@ cp -p common.gypi %{buildroot}%{_datadir}/node
 
 %files docs
 %{_defaultdocdir}/%{name}-docs-%{version}
-%doc LICENSE
 
 %changelog
+* Thu Jan 31 2013 Jason Antman <Jason.Antman@cmgdigital.com> - 0.9.5-10
+- specify build requirement of c-ares-devel >= 1.9.0
+- specify build requirement of libuv-devel 0.9.4
+- remove duplicate %doc LICENSE that was causing cpio 'Bad magic' error on CentOS6
+
 * Sat Jan 12 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 0.9.5-9
 - fix brown paper bag bug in requires generation script

v8.spec, diff from Fedora Rawhide 3.13.7.5-2 (full specfile)

--- v8.spec.orig       2013-01-26 16:03:18.000000000 -0500
+++ v8.spec     2013-01-31 09:04:51.068029459 -0500
@@ -21,9 +21,11 @@
 
 # %%global svnver 20110721svn8716
 
+%{!?python_sitelib: %define python_sitelib %(%{__python} -c "import distutils.sysconfig as d; print d.get_python_lib()")}
+
 Name:          v8
 Version:       %{somajor}.%{sominor}.%{sobuild}.%{sotiny}
-Release:       2%{?dist}
+Release:       5%{?dist}
 Epoch:         1
 Summary:       JavaScript Engine
 Group:         System Environment/Libraries
@@ -32,7 +34,7 @@
 Source0:       http://commondatastorage.googleapis.com/chromium-browser-official/v8-%{version}.tar.bz2
 BuildRoot:     %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
 ExclusiveArch: %{ix86} x86_64 %{arm}
-BuildRequires: scons, readline-devel, libicu-devel
+BuildRequires: scons, readline-devel, libicu-devel, ncurses-devel
 
 %description
 V8 is Google's open source JavaScript engine. V8 is written in C++ and is used 
@@ -51,8 +53,13 @@
 %setup -q -n %{name}-%{version}
 
 # -fno-strict-aliasing is needed with gcc 4.4 to get past some ugly code
-PARSED_OPT_FLAGS=`echo \'$RPM_OPT_FLAGS -fPIC -fno-strict-aliasing -Wno-unused-parameter -Wno-error=strict-overflow -Wno-error=unused-local-typedefs -Wno-unused-but-set-variable\'| sed "s/ /',/g" | sed "s/',/', '/g"`
+%if 0%{?el5}
+PARSED_OPT_FLAGS=`echo \'$RPM_OPT_FLAGS -fPIC -fno-strict-aliasing -Wno-unused-parameter -lncurses\'| sed "s/ /',/g" | sed "s/',/', '/g"`
+sed -i "s|'-O3',|$PARSED_OPT_FLAGS,|g" SConstruct
+%else
+PARSED_OPT_FLAGS=`echo \'$RPM_OPT_FLAGS -fPIC -fno-strict-aliasing -Wno-unused-parameter -Wno-error=strict-overflow -Wno-unused-but-set-variable\'| sed "s/ /',/g" | sed "s/',/', '/g"`
 sed -i "s|'-O3',|$PARSED_OPT_FLAGS,|g" SConstruct
+%endif
 
 # clear spurious executable bits
 find . \( -name \*.cc -o -name \*.h -o -name \*.py \) -a -executable \
@@ -198,6 +205,17 @@
 %{python_sitelib}/j*.py*
 
 %changelog
+* Thu Jan 31 2013 Jason Antman <Jason.Antman@cmgdigital.com> - 1:3.13.7.5-5
+- remove -Werror=unused-local-typedefs on cent6
+
+* Wed Jan 30 2013 Jason Antman <Jason.Antman@cmgdigital.com> - 1:3.13.7.5-4
+- define python_sitelib if it isn't already (CentOS 5)
+
+* Wed Jan 30 2013 Jason Antman <Jason.Antman@cmgdigital.com> - 1:3.13.7.5-3
+- pull 3.13.7.5-2 SRPM from Fedora 19 Koji most recent build
+- add ncurses-devel BuildRequires
+- modify PARSED_OPT_FLAGS to work with g++ 4.1.2 on CentOS 5
+ 
 * Sat Jan 26 2013 T.C. Hollingsworth <tchollingsworth@gmail.com> - 1:3.13.7.5-2
 - rebuild for icu-50
 - ignore new GCC 4.8 warning

Fedora Init Script Specification Summary

I’ve been deploying some new software lately (specifically selenesse, which combines Selenium and fitnesse, xvfb). None of these seem to come with init scripts to run as daemons, and the quality of the few Fedora/RedHat/CentOS init scripts I was able to find was quite poor. The Fedora project has a Specification for SysV-style Init Scripts in their Packaging wiki, which specifies what a Fedora/RedHat/CentOS init script should look like, in excruciating detail. What follows is an overview of the more important points, which I’m using to develop or modify the scripts I’m currently working on.

  • Scripts must be put in /etc/rc.d/init.d, not in the /etc/init.d symlink. They should have 0755 permissions.
  • Scripts must have a Fedora-style chkconfig header (“chkconfig:”, “description:” lines), and may have an LSB-style header (BEGIN INIT INFO/END INIT INFO). See Initscript template.
  • Scripts must make use of a lockfile in /var/lock/subsys/, and the name of the lockfile must be the same as the name of the init script. (There is a technical reason for this relating to how sysv init terminates daemons at shutdown). The lockfile should be touched when the daemon successfully starts, and removed when it successfully stops.
  • Init scripts should not depend on any environment variables set outside the script. They should operate gracefully with an empty/uninitialized environment (or only LANG and TERM set and a CWD of /, as enforced by service(8), or with a full environment if they are called directly by a user.
  • Required actions – all of the following actions are required, and have specific definitions:
    • start: starts the service
    • stop: stops the service
    • restart: stop and restart the service if the service is already running, otherwise just start the service
    • condrestart (and try-restart): restart the service if the service is already running, if not, do nothing
    • reload: reload the configuration of the service without actually stopping and restarting the service (if the service does not support this, do nothing)
    • force-reload: reload the configuration of the service and restart it so that it takes effect
    • status: print the current status of the service
    • usage: by default, if the initscript is run without any action, it should list a “usage message” that has all actions (intended for use)
  • There are specified exit codes for status actions and non-status actions.
  • They must “behave sensibly”. I’ve found this to be one of the biggest problems with homegrown init scripts. If servicename start is called while the service is already running, it should simply exit 0. Likewise if the service is already stopped. Init scripts must not kill unrelated processes. I don’t know how many times I’ve seen scripts that kill every java or python process on a machine.

I intend to use this as a quick checklist when developing or evaluating init scripts for RedHat/Fedora based systems. In my experience, the biggest problems with most init scripts revolve around poor handling of PID files and lockfiles, mainly:

  • Killing processes other than the one that the script started (i.e. killing all java or python processes), usually because the PID isn’t tracked at start
  • Starting a second instance of the subsystem because lockfiles aren’t used, or the status function is broken.
  • improper exit codes
  • either explicitly relying on environment variables (and therefore breaking when called through service(8)), or conversely, not cleaning/resetting environment variables that are used by dependent code or processes.

CentOS/Fedora Install on SuperMicro Servers via IPMI Card KVM Over IP

I recently had to setup Fedora 16 to test something on a SuperMicro 6015T-TV 1U “dual” server. This is a 1U enclosure with two separate servers in it – based on the SuperMicro X7DBT motherboards – each with an AOC-SIMSO+ IPMI and KVM-over-IP management card. Every time I tried different options for the kernel parameters (I was using Cobbler), I could get the OS to boot, but once Anaconda started, I’d lose image (“No Signal”) on the IP KVM, and the serial console would go quiet. It took me about a dozen tries before I found a mailing list reference to the “nomodeset” option. This did the trick perfectly, and kept Anaconda working.

php-suhosin syslog issues

I just installed php-suhosin 0.9.29 from EPEL on a CentOS 5.6 box. I’m running a whole bunch of name-based vhosts in Apache, and have a bunch of web apps, so I opted to run suhosin in simulation mode (don’t actually block anything, but log errors) and have it log via syslog to a single file. Unfortunately, when I configured this, the syslog messages started showing up in the wrong place, apparently with the wrong facility and priority. After some roundabout debugging (at first assuming syslogd to be the problem), I determined that, for whatever really strange reason (perhaps an incorrect syslog.h on the EPEL box that built the suhosin package?) the LOG_* constants were incorrect. I looked up the correct integer values in /usr/include/sys/syslog.h and the following configuration directives accomplished the task correctly:

suhosin.log.syslog.facility = 128
; 128 = LOG_LOCAL0
 
suhosin.log.syslog.priority = 5
; 5 = LOG_NOTICE

This one line puts suhosin into simulation mode, where it only logs errors instead of enforcing on them:

suhosin.simulation = On

pnp4nagios, CentOS 5.3 and pcre

I started testing out the pnp4nagios tool to incorporate graphs of performance data into Nagios. Despite what Klein and Sellens suggest (p. 57), I really don’t want separate tools for monitoring and trending. Cactialready handles UPS metrics, switch ports, router traffic, etc. For everything else – system load, etc. – I see no reason to have two checks run rather than just one (Nagios).

There was a CentOS package for the older pnp4nagios 0.4.x, but I opted to build and install the new 0.6.x from source. Unfortunately, I hit one snag – it requires PCRE compiled with support for Unicode properties, and I couldn’t find any package for CentOS compiled with that option. So, with a simple edit of the %configure macro in the SPEC file, I built one. Unfortunately, I wasn’t working in a real build environment – just on one of my web servers – so I only built the .i386 version, but you can feel free to build from the source rpm.

rsyslog on CentOS5

Update July 2011 – We’ve been using rsyslog for our centralized syslog infrastructure at work (site being redesigned at the moment) for about a year now. As a result, our colleagues at Rutgers University Open System Solutions have been nice enough to include rsyslog in their koji build system. Updated packages of rsyslog for CentOS 5 x86 and x86_64 are available here. Please be aware that they have some dependencies of specific RU versions. It’s probably best if you download the source RPM and build it yourself using rpmbuild. The current 5.6.2 source package includes a CentOS 5 spec file and other related scripts. This RPM repository is only open to the public as a courtesy, so please download once at most and distribute it to your servers yourself.

Having finally setup my storage server (I know it’s not much, but for me starting with 1TB is wonderful), I actually got around to redoing my centralized logging infrastructure. Here’s a small summary of what I have to handle:

  • logs from 15 hosts at 2 locations, including Cisco devices and a mix of syslog and syslog-ng Linux boxen.
  • Remote location logs forwarded to one server at remote location, then to centralized log server via SSH port forwarding.
  • 48-hour retention of full iptables border firewall logs (~ 3GB/day).
  • Future plans to have all logs stored in MySQL.

I’d previously had most of the boxes logging to an older host with low disk space, but had to discontinue this due to lack of storage. Having assessed the options, and with definite plans to log to a database, I decided to go with rsyslog for the centralized host.

Unfortunately, stable rsyslog is up to 4.4.x, with 5.x in development, and the newest package I could find for CentOS was 2.something. So, I set about building it from source. It was a *very* difficult build on my machine (CentOS 5.3, 2.6.18-128.el5 #1 SMP i686). Unfortunately, I don’t have an RPM build environment setup, but here’s how I accomplished it:

  1. I f not already done, yum install rpmforge-release
  2. yum install gnutls-devel gnutls libatomic_ops-devel gcc43 java-1.6.0-openjdk-devel
  3. Download rsyslog-4.5.2 (currently Beta). Extract, cd into the directory.
  4. To test the performance difference from MySQL, yum install php-pgsql postgresql postgresql-devel postgresql-libs postgresql-server
  5. Edit the “configure” script, add -DHAVE_ATOMIC_BUILTINS to the DEFS line (28936 in this version).
  6. export CC=/usr/bin/gcc43
  7. export CCDEPMODE=”depmode=gcc4″
  8. export CFLAGS=”-O3 -march=i686″
  9. ./configure –enable-mysql –enable-omtemplate –enable-gnutls –enable-pgsql
  10. make && make install

For my system, I used the /etc/sysconfig/rsyslog and /etc/init.d/rsyslog from the 2.x rsyslog RPMs, with some modifications as follows:

/etc/sysconfig/rsyslog (comments have been removed to save space):

# -c4   version 4 compatibility mode
# -x     disable DNS for remote messages (don't want it to hang if DNS is down
# -4     IPv4 only
SYSLOGD_OPTIONS="-c4 -x -4"

/etc/init.d/rsyslog (comments have been removed to save space):

#!/bin/bash
#
# rsyslog        Starts rsyslogd.
#
#
# chkconfig: - 12 88
# description: Syslog is the facility by which many daemons use to log \
# messages to various system log files.  It is a good idea to always \
# run rsyslog.
### BEGIN INIT INFO
# Provides: $syslog
# Required-Start: $local_fs $network $remote_fs
# Required-Stop: $local_fs $network $remote_fs
# Default-Stop: 0 1 2 3 4 5 6
# Short-Description: Enhanced system logging and kernel message trapping daemons
# Description: Rsyslog is an enhanced multi-threaded syslogd supporting,
#              among others, MySQL, syslog/tcp, RFC 3195, permitted
#              sender lists, filtering on any message part, and fine
#              grain output format control.
### END INIT INFO
 
# Source function library.
. /etc/init.d/functions
 
RETVAL=0
 
start() {
        [ -x /sbin/rsyslogd ] || exit 5
 
        # Do not start rsyslog when sysklogd is running
        if [ -e /var/run/syslogd.pid ] ; then
                echo $"Shut down sysklogd before you run rsyslog";
                exit 1;
        fi
 
        # Source config
        if [ -f /etc/sysconfig/rsyslog ] ; then
                . /etc/sysconfig/rsyslog
        else
                SYSLOGD_OPTIONS="-m 0"
        fi
 
        if [ -z "$SYSLOG_UMASK" ] ; then
              SYSLOG_UMASK=077;
        fi
        umask $SYSLOG_UMASK
 
        echo -n $"Starting system logger: "
        daemon rsyslogd $SYSLOGD_OPTIONS
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ] && touch /var/lock/subsys/rsyslog
        return $RETVAL
}
stop() {
        echo -n $"Shutting down system logger: "
        killproc rsyslogd
        RETVAL=$?
        echo
        [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/rsyslog
        return $RETVAL
}
reload()  {
    RETVAL=1
    syslog=`cat /var/run/rsyslogd.pid 2>/dev/null`
    echo -n "Reloading system logger..."
    if [ -n "${syslog}" ] && [ -e /proc/"${syslog}" ]; then
        kill -HUP "$syslog";
        RETVAL=$?
    fi
    if [ $RETVAL -ne 0 ]; then
        failure
    else
        success
    fi
    echo
    return $RETVAL
}
rhstatus() {
        status rsyslogd
}
restart() {
        stop
        start
}
 
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  restart)
        restart
        ;;
  reload|force-reload)
        reload
        ;;
  status)
        rhstatus
        ;;
  condrestart)
        [ -f /var/lock/subsys/rsyslog ] && restart || :
        ;;
  *)
        echo $"Usage: $0 {start|stop|restart|reload|force-reload|condrestart}"
        exit 2
esac
 
exit $?

My rsyslog.conf file:

# uncomment next line for debugging, use graphvis to see the graph
#$GenerateConfigGraph /root/rsyslog-graph.dot
$ModLoad imklog
$ModLoad imtcp
$ModLoad imudp
$ModLoad imuxsock
 
# template
$template RemoteHost,"/var/log/HOSTS/%HOSTNAME%/%$YEAR%/%$MONTH%/%$DAY%/%syslogfacility-text%.log"
# used for Cisco, vanilla syslog when we can't parse host name
$template RemoteFromHost,"/var/log/HOSTS/%FROMHOST%/%$YEAR%/%$MONTH%/%$DAY%/%syslogfacility-text%.log"
 
# NOTE - we can't bind UDP to a ruleset, so it enters the local RuleSet
#   and has to be dealt with here
 
$RuleSet local
 
# for cisco, vyatta - doesn't send hostname, need to use IP manually
:fromhost, isequal, "192.168.0.99" ?RemoteFromHost
:fromhost, isequal, "192.168.0.99" ~
:fromhost, isequal, "192.168.0.103" ?RemoteFromHost
:fromhost, isequal, "192.168.0.103" ~
:fromhost, isequal, "192.168.0.97" ?RemoteFromHost
:fromhost, isequal, "192.168.0.97" ~
:fromhost, isequal, "192.168.0.111" ?RemoteFromHost
:fromhost, isequal, "192.168.0.111" ~
# anything from a remote host gets logged as such
:source, isequal, "" ?RemoteHost
:source, isequal, "" ~
 
#
# LOCAL LOGGING
#
 
kern.*                                                 /var/log/messages
*.info;mail.none;authpriv.none;cron.none                /var/log/messages
authpriv.*                                              /var/log/secure
mail.*                                                  -/var/log/maillog
cron.*                                                  /var/log/cron
*.emerg                                                 *
uucp,news.crit                                          /var/log/spooler
local7.*                                                /var/log/boot.log
 
# use the local RuleSet as default
$DefaultRuleset local
 
#
# BEGIN centralized logging stuff added 2009-09-16 by jantman
#
 
# define ruleset for remote logging
$RuleSet remote
 
*.* ?RemoteHost
 
# bind ruleset to tcp listener
$InputTCPServerBindRuleset remote
# and activate it:
$InputTCPServerRun 5000
 
$UDPServerRun 514
$UDPServerRun 5000
 
#
# END remote logging
#