Make forever for node.js as a Linux service

由于个人比较喜欢在 Linux 上使用 service ,所以才有了以下的折腾。

forever 与 supervisor 的选择

其实,在本地开发还是比较喜欢用 supervisor 的,因为命令简单。考虑到服务器上可能会运行多个 node server 实例,因此选择 forever 来做 node 服务的管理。

了解 forever ,戳 https://github.com/nodejitsu/forever
了解 supervisor,戳https://github.com/isaacs/node-supervisor

Step 1

安装 forever

1
sudo npm install -g forever

(这货依赖不是一般多……)

安装成功后可以看到安装路径,记下待用。以下是我的服务器环境的返回:

1
/usr/local/lib/node_modules

注意:在 CentOS 上安装 nodejs / npm 的方式不同,会得到不同的安装路径:make install 一般在 /usr/local/lib/node_modules;yum install 一般在 /usr/lib/node_modules 。

Step 2

创建启动文件

1
sudo vim /etc/init.d/the_name_you_want

启动文件内容

1
2
3
#!/bin/bash
# chkconfig: 345 99 01
# description: forever service

345 是启动级别,99 是启动优先级(序号),01 是关闭优先级(序号)。如果有其他服务依赖于 forever 服务,可以提高优先级,以确保其他服务启动时其可用。

继续:

1
2
3
DEAMON="/your/server/config/file/path"
LOG="/your/logs/xxx.log"
PID="/root/.forever/pids/xxx.pid"
  • DEAMON 处给出配置文件地址,其中文件内容每行为一项任务,每项形如:
    name app.js path/of/app/js

  • 两处“xxx”是占位符,稍后替换用;

注意右边是值在引号内!!不然 forever 可能会喊它要 string,尤其是 LOG 地址。在 yum 安装环境中遇到过,make 安装环境未重现。

继续

1
2
3
export PATH=$PATH:/usr/local/bin
export NODE_PATH=$NODE_PATH:/usr/local/lib/node_modules
export NODE_ENV=production

注入环境变量,不然找不到 node 或者 -g 安装的包。上面已经提过,地址根据安装方法不同不一致,区别在于有没有 local 这一级,自己根据实际情况定夺。

继续,start 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case "$1" in
start)
while read Name Main Path
do
if [ $2 ]
then
if [ "$2" != "$Name" ]
then
continue
fi
fi
if [ -f ${PID/xxx/$Name} ]
then
echo -e "\033[0;33mWarn:\033[0m \033[0;32m$Name\033[0m is already running well."
continue
fi
forever start -wal ${LOG/xxx/$Name} -m 0 --watchDirectory $Path --pidFile ${PID/xxx/$Name} $Path/$Main
done < $DEAMON
;;

详细说明一下:

  • “Name Main Path” 对应配置文件中的内容格式,
  • start 后不跟参数,则启动所有配置项;跟上配置项 Name 做参数则仅启动该项
  • 已经启动的返回友好提示信息

关于 forever 的说明:

  • -w 监视文件的变更,一旦有变更,forever 会重启关联 server
  • -a 声明追加内容到已有日志文件 (没有 -a 重启时会直接报错并中断…………谁会每次重新启动来关心 log 日志?!为什么 -a 不是默认值!!!)
  • -l 指定日志文件地址
  • -m 最大运行次数
  • —watchDirectory 监视文件的目录 path
  • —pidFile 字面意思

list 命令用来查看正在运行的进程,其他命令顾名思义了。

继续,stop 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
stop)
if [[ `forever list` =~ 'No' ]]
then
forever list
else
echo '======================================'
forever list
echo '======================================'
echo -e "Enter the number of uid[\033[0;32mX\033[0m] to stop."
printf 'uid: '
read answer
echo -e "\033[0;32mStopping\033[0m $answer..."
echo '======================================'
forever stop $answer || echo 'Wrong entry!\rExit!\rRetry.'
echo '======================================'
echo -e "\033[0;32mDone!\033[0m"
fi
;;

说明

  • 不需要第二参数
  • 显示正在运行的任务列表,输入 uid 号以停止相应任务

继续,stopall、restartall 命令:

1
2
3
4
5
6
stopall)
forever stopall
;;
restartall)
forever restartall
;;

很简单,不错解释了。

继续,reload|restart 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
reload|restart)
if [ $2 ]
then
while read Name Main Path
do
if [ "$Name" = "$2" ]
then
forever restart -wal ${LOG/xxx/$Name} -m 0 --watchDirectory $Path --pidFile ${PID/xxx/$Name} $Path/$Main
fi
done < $DEAMON
else
echo -e "\033[0;31merror\033[0m: need second param like this: $1 name_of_the_app"
fi
;;

用法说明

  • 必须使用第二参数:任务项的 Name
  • reload、restart 互为别名

继续,获取运行任务列表:

1
2
3
list)
forever list
;;

没有命令时:

1
2
3
4
5
6
  *)
echo "Usage: /etc/init.d/noded {start|stop|restart|reload|stopall|restartall|list}"
exit 1
;;
esac
exit 0

到这里,启动文件创建完成了,可以 :wq 了。

Step 3

确认启动文件的一些属性

1
2
sudo chown root:root /etc/init.d/the_name_you_want
sudo chmod 755 /etc/init.d/the_name_you_want

启动服务

1
2
sudo chkconfig --add the_name_you_want
chkconfig the_name_you_want on

基本完成

可以通过如下命令来控制服务了

1
2
3
4
sudo service the_name_you_want start
sudo service the_name_you_want restart
sudo service the_name_you_want stop
sudo service the_name_you_want list

其他问题

添加忽略监控文件的配置文件

1
touch /your/watch/path/.foreverignore

如果没有 .foreverignore 会报一个 error,如下:

1
2
error: Could not read .foreverignore file.
error: ENOENT, open '/your/watch/path/.foreverignore'

但是你的 app.js 是会正常执行的,应该说一切正常。

而,如果没有 —watchDirectory 参数配置,你仍然会得到一个 error,如下:

1
2
3
4
5
6
7
error: Could not read .foreverignore file.
error: ENOENT, open '/.foreverignore'

/usr/local/lib/node_modules/forever/node_modules/forever-monitor/node_modules/watch/main.js:63
if (err) throw err;
^
Error: ENOTDIR, readdir '/proc/1/fd/5'

此时,你的 app.js 依然欢快运行,但是你执行 list 命令去看它的时候,它……

1
2
sudo /etc/init.d/noded list
info: No forever processes running

-w 和 —watchDirectory 要一起出现,就够了,-w 不能直接接收值也忍了,不设置后者直接出现这种奇异现象,真是……(此处省略10000字!!)


End

在 Mac 上用 ImageMagick 转换 SVG

使用 ImageMagick

ImageMagick 是老牌的图片解决方案了,这次在 node.js 项目里本想用它的一个演进项目 GraphicsMagick,只可惜,在同样使用 gm 这个 npm 的情况下,GraphicsMagick 对中文的支持太不好了,于是在折腾一番后,还是选择放弃尝试新的事物,转为继续使用各方面技术支持更广泛的老大哥 ImageMagick。

我的需求是需要将 SVG 转为 Raster Graphics (位图),例如将如下 SVG 转为 JPG:

http://www.w3.org/TR/2009/WD-SVGCompositing-20090430/examples/over.svg

结果转换成 JPG 后,图片变成:

内容细节严重丢失,内嵌图片不见了。

经过各种参数调整,可问题依然。此时,查看官方文档发现一些问题:

ImageMagick 对 SVG 支持三种格式:MSVG, SVG, SVGZ。MSVG 为 ImageMagick 的内建库;SVG 和 SVGZ 则依赖其他一些库,比如,官方推荐的 RSVG。

于是用命令查看对 SVG 的支持情况:

1
convert -list format | grep SVG

返回

1
2
3
MSVG  SVG       rw+   ImageMagick's own SVG internal renderer
SVG SVG rw+ Scalable Vector Graphics (XML 2.7.8)
SVGZ SVG rw+ Compressed Scalable Vector Graphics (XML 2.7.8)

XML?!坑爹啊!果然不是 RSVG 库,还不是得很奇葩……

好吧!装库!

一堆问题

先安装 librsvg 库,在 Mac 下我目前采用 brew 解决包管理,所以:

1
brew install librsvg

结果报错,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
librsvg: Unsatisfied dependency: XQuartz
Homebrew does not package XQuartz. Installers may be found at:
https://xquartz.macosforge.org
cairo: Unsatisfied dependency: XQuartz
Homebrew does not package XQuartz. Installers may be found at:
https://xquartz.macosforge.org
pango: Unsatisfied dependency: XQuartz
Homebrew does not package XQuartz. Installers may be found at:
https://xquartz.macosforge.org
gtk+: Unsatisfied dependency: XQuartz 2.3.6
Homebrew does not package XQuartz. Installers may be found at:
https://xquartz.macosforge.org
Error: An unsatisfied requirement failed this build.

这才发现,原来 Mac 10.8 上没有 X11,好吧,安装一个,Apple 官方推荐的下载地址:

http://xquartz.macosforge.org/landing/

是 dmg 文件,如何安装就是不用说了。XQuartz 安装完之后,再次运行安装 librsvg 的命令就势如破竹了。

librsvg 安装好之后需要重新安装 ImageMagick,网上找到 —use-rsvg 这个参数,但结果是又一次坑爹了。一阵安装编译等待之后,依旧显示 (XML 2.7.8)!!!什么情况呢?重新进入无尽的搜索,最后是从 brew 源代码里找到当前 brew 版本中的正确参数 —with-librsvg。源码见:

https://github.com/mxcl/homebrew/blob/master/Library/Formula/imagemagick.rb

结果执行如下命令就成功了…

1
brew reinstall imagemagick --with-librsvg --with-webp

检验一下:

1
2
3
4
convert -list format | grep SVG
MSVG SVG rw+ ImageMagick's own SVG internal renderer
SVG SVG rw+ Scalable Vector Graphics (RSVG 2.36.3)
SVGZ SVG rw+ Compressed Scalable Vector Graphics (RSVG 2.36.3)

另外,如果此时还有问题,识别不到 RSVG 的话,请添加以下环境变量:

1
export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/opt/X11/lib/pkgconfig

而,如果是使用 port 的,命令如下:

1
port install imagemagick +rsvg

按理到这里为止,ImageMagick 已经可以正常通过 librsvg 来渲染 SVG 格式的图片了,但是在我的 Mac 环境里始终存在丢失 xlink 进来的外链 image 的问题。于是又进入了茫然……

不要妄自菲薄

为了解决 node.js 下使用 ImageMagick 来处理图片的问题,前后折腾了几个小时,当所有已知问题都解决的时候一切回到原点。很不甘,是哪儿弄错了?于是又折腾了几个小时……

由于使用场景和使用的细节已然是少数需求了,国内相关文字几乎为零,英文站点上的内容也极少。stackoverflow 上尽是提问的,回答了了,更多的是根本没有回答。

突然,因为连续几篇文字提到了 ImageMagick 的不同版本对于类似的 convert 时出现的 bug,于是我怀疑是我环境里 ImageMagick 版本的问题。立刻在服务器环境中进行了测试(ImageMagick 6.5.4-7 with RSVG 2.26.0 on CentOS)果然,一切正常了!OMG!

出错的是 ImageMagick 6.8.6-3 with RSVG 2.36.3 版本,貌似是 RSVG 的错误,具体哪里出错暂时无力再去验证。具体错误表现是:

  • xlink:href=”abc.png” 同目录丢失
  • xlink:href=”./abc.png” 相对路径丢失
  • xlink:href=”/path/to/abc.png” 绝对路径丢失
  • xlink:href=”data:image/png;base64,…=” 内嵌 Base64 格式正常

最后得到正确的图像输出:


此时,我想说,虽然常提“不要妄自菲薄”,但是做到还是挺难的。质疑问题需要的是锲而不舍的精神和精湛的技艺,我辈还不具备两者,还没勇气去怀疑知名软件库,真是还需努力!

新的开始

被迫“创新”

有的时候,创新是被迫的,某种无奈产生时,只能被迫寻求解决方案,由于具备一颗折腾和不满足的心,创新也就成了随之而来的必然选择。

这次是忘记续费域名。之前博客域名的注册商不怎么靠谱,似乎并未向我发送域名到期的提醒邮件,等到想再写博客的时候,发现网站访问不了,我的域名被抢注了……

本打算放弃回复个人网站了,但偶尔写了技术文字,还想发一下,找不到合适的地方,只好再寻好的域名。其实一直以来都想注册 .gu 域名,就写了一份蹩脚英文邮件给 .gu 的域名管理者,表达了希望注册他们域名的愿望,可是始终等不到回复,只好作罢。目光瞄向其他奇特域名的时候,意外发现 zgu.me 竟然是可以注册的,短且达意,完全符合标准,于是才有了这篇文字和这个新域名的应用。

搞定域名后,新的问题是用什么来承载网站,这才发现 WordPress 已不再风靡于 Geek 群体,而是 Octopress 很受欢迎。结果有了本站的架构,此处省去一万字如何部署和中途困难解决的文字。

经过这些折腾,突然有感于人生可能多为被迫成事,尤其是创造性、创新性的事儿。

深挖“坑洞”

Octopress目前少有满意的主题,突然萌发了开发其主题的想法。由于忙于其他事物,不知该想法是否只是挖坑不填,但还是先挖为快!

新的“开始”

写完这篇也算完成了新的开始,同时人生最近筹划着很多新的开始。希望一切顺利,也好有机会在这里继续分享新的开始。

完。