サービスの設定を簡単にする setconfig コマンド

Linuxでサービスの設定や解除を行う場合、GNOME等の"サービスの設定"ツールとかを使うのが一般的だが、遠隔からコマンドラインで設定したり、シェルスクリプトから設定する場合は結構手間がかかる。Redhat系の場合は、"chkconfig"というコマンドを使って一つ一つのサービスに対して設定を繰り返す必要がある。作業数も多くなり設定ミスの原因ともなりかねない。また、ランレベル毎の設定をきめ細かく行う場合はさらに手間がかかる。

そこで、(全てのRun Levelに対して)各サービスをまとめて設定するコマンドを作ってみた。

まず、サービスの設定を定義するデータファイルを作成する。(次の例では "servicelist" というファイルにサービスの設定が記述してある。)

[root@fedora-8 ~]# cat servicelist
avahi-daemon    0:off   1:off   2:off   3:on    4:on    5:on    6:off
bluetooth       0:off   1:off   2:on    3:on    4:on    5:on    6:off
irda            0:off   1:off   2:off   3:off   4:off   5:off   6:off
isdn            0:off   1:off   2:on    3:on    4:on    5:on    6:off
lm_sensors      0:off   1:off   2:off   3:off   4:off   5:off   6:off
sendmail        0:off   1:off   2:on    3:on    4:on    5:on    6:off
sshd            0:off   1:off   2:on    3:on    4:on    5:on    6:off
[root@fedora-8 ~]# 

このデータファイルを引数として次のように実行するだけ。

[root@fedora-8 ~]# setconfig servicelist

これでリストに定義しているサービスのそれぞれのランレベルに関しての設定を全て行う。リストのフォーマットはご覧の通り "chkconfig --list" の出力形式と同じなので、ひな形リストは chkconfig を使って生成すればいい。

[root@fedora-8 ~]# chkconfig --list > servicelist
[root@fedora-8 ~]# vi servicelist
	:
	:
[root@fedora-8 ~]# setconfig servicelist

コマンドの引数は以下の通り。

Usage: setconfig [-qv] [--help] [config_list]

"-q"はメッセージを一切出さない。"-v"は逆に色々とメッセージを出すようにする。"config_list"がデータとなるリストで、ファイルの指定がなければ標準入力から読み込む。従って、次のような利用も可能。(この例では bluetoothisdn のサービスを全てのランレベルでoffにして設定している。)

[root@fedora-8 ~]# chkconfig --list | grep -e bluetooth -e isdn | sed -e 's/on/off/g' | setconfig -v

サービス設定リストのフォーマット

setconfigに与えるサービス設定リストのフォーマットについては次のようの設定した。

  1. 基本的には "chkconfig --list" の形式。空白もしくはタブで区切られている。(従って、空白やタブで区切られていれば整列する必要はない。)
  2. 行頭に "#" がある行は無視する。コメント行として扱う。
  3. xinetd起動のサービスについては対象範囲外とする。
  4. ランレベルは0〜6まで全て定義しなくて良い。設定が必要なランレベルのだけ記述れば良い。記述されていないランレベルに関しては変化しない。(以前の設定のまま残る。)例えば、次のようなリストでOK。
avahi-daemon    2:off   3:off   4:off   5:on
# bluetooth is off at run-level 3
bluetooth       3:off
irda            2:off 3:off 4:off 5:off
isdn            2:on            4:on    5:off

あとは余り説明の必要もないだろう。

インストールについて

setconfig はシェルスクリプトとして記述されたプログラムであり、「Source list of "setconfig"」にプログラムを載せているので、利用する場合はコピー&ペーストでシェルスクリプトファイルとして保存するだけ。例えば、次の要領でファイルに保存し、実行権を与えるだけ。

[root@fedora-8 ~]# cat > /usr/local/bin/setconfig
【ブログからコピーしたプログラムをペーストする。最後に Ctrl-D を入力して終了。】
[root@fedora-8 ~]# chmod +x /usr/local/bin/setconfig

なお、setconfigの引数の処理に "shargs" という別のプログラムを利用している。setconfigを利用する場合にはshargsも必要になる。ただ、shargsも同様にシェルスクリプトとして書かれているので、上記と同じ要領で簡単にインストールできる。(shargsについては「シェルスクリプトの引数を処理するためのシェルスクリプト」を参照。プログラムは「program list of "shargs" without line-number」からコピーしてくる。)shargsは /usr/local/bin に置いてあるという前提で setconfigは作っているので、もし異なる場所に設置しているのであれば setconfigの中の ". /usr/local/bin/shargs"の部分を適宜変える。

プログラムについて

プログラムにつては特段説明も必要ないと思う。1つだけ言い訳をしておくと、プログラムの中でreadコマンドを使って設定リストを1行1行読み込み、各フィールドをシェル変数に代入している。今回はあえて「配列変数」は使わなかった。配列変数を使えばもう少し(ちょっとだけだが)プログラムの記述が簡略化できるが、元々、Bourne Shellには配列シェル変数はなかったので、どうも配列を使うのをためらってしまう。(個人的な趣味の問題だが。)

あと、setconfigが出力するメッセージに関していくつかのレベルに分けている。

レベル 意味
II 単なる情報。余り意味はない。
QQ オペレータからの入力を必要とするメッセージ。今回は使っていない。
WW 警告メッセージ。プログラムの実行には差し支えない。
EE エラーメッセージ。プログラムの実行に影響する。(通常はexitする。)
-- その他。
XX メッセージの定義が不明の場合に出力。

Xウィンドウのログ、/var/log/Xorg.0.logを見ていて、ログがレベル分けしてあるので問題ヵ所を見つけやすいと思っていた。そこで、それに似たログの出力にしてみた。

Source list of "setconfig"

#! /bin/bash

# setconfig Ver. 0.001 (2008/07/20)
# Copyright (C) 2008 Adsaria

# This program is free software; you can redistribute it and/or modify it.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY.

### Argument handling ##########################################################

ARGS_DEF_opt_char_flg="qv"
ARGS_DEF_opt_word_flg="help"
ARGS_DEF_arg_namelist="config_list"
ARGS_DEF_arg_num_must=0

. /usr/local/bin/shargs

if [ "${ARGS_error}" == "Y" ]; then echo "${ARGS_usage}" 1>&2 ; exit 1; fi
if [ "${ARGS_opt_help}" == "Y" ]; then echo "${ARGS_usage}" 1>&2 ; exit 1; fi
if [ "${ARGS_opt_q}" == "Y" ]; then QUIET="yes"; else QUIET="" ; fi
if [ "${ARGS_opt_v}" == "Y" ]; then CHATTY="yes"; else CHATTY="" ; fi
if [ "${QUIET}" == "yes" ]; then CHATTY=""; fi

### Define shell vars ##########################################################

CMD_NAME=`basename ${ARGS_0}`

### Define local functions #####################################################

function msg () {
        # Return Code 0: massage out, 1: message hold, 2: error
        ECHO_FLAG=
        if [ "${1}" == "-n" ]; then ECHO_FLAG="${ECHO_FLAG} -n"; shift; fi
        if [ -z "${1}" -o -z "${2}" ]; then return 2; fi
        MSG_CATEGORY="${1}"
        MSG_BODY="${2}"
        DO_ECHO="yes"
        case "${MSG_CATEGORY}" in
        II|I|ii|i)
                MSG_CATEGORY="II"
                if [ "${CHATTY}" != "yes" ]; then DO_ECHO="no"; fi
                ;;
        --|-)
                MSG_CATEGORY="--"
                if [ "${CHATTY}" != "yes" ]; then DO_ECHO="no"; fi
                ;;
        QQ|Q|qq|q)
                MSG_CATEGORY="QQ"
                ;;
        WW|W|ww|w)
                MSG_CATEGORY="WW"
                if [ "${QUIET}" == "yes" ]; then DO_ECHO="no"; fi
                ;;
        EE|E|ee|e)
                MSG_CATEGORY="EE"
                if [ "${QUIET}" == "yes" ]; then DO_ECHO="no"; fi
                ;;
        *)
                MSG_CATEGORY="XX"
                ;;
        esac
        if [ "${DO_ECHO}" == "yes" ]; then
                echo $ECHO_FLAG "(${MSG_CATEGORY}) $MSG_BODY" 1>&2
        fi
}

### Check arguments ############################################################

CONFIG_LIST_FILE=
if [ -n "${ARGS__config_list}" ]; then
	if [ "${ARGS__config_list}" != "-" ]; then
		if [ -f ${ARGS__config_list} ]; then
			CONFIG_LIST_FILE=${ARGS__config_list}
			exec < ${CONFIG_LIST_FILE}
		else
			msg e "\"${ARGS__config_list}\" does not exist or is not a file."
			exit 1
		fi
	fi
fi

################################################################################

while read SERVICE LEVEL0 LEVEL1 LEVEL2 LEVEL3 LEVEL4 LEVEL5 LEVEL6 ; do
	if [ -z "${SERVICE}" ]; then continue; fi
	if [ "${SERVICE::1}" == "#" ]; then continue; fi
	if [ "${SERVICE}" == "xinetd" ]; then
		msg i "ignoring \"xinetd\" services."
		break
	fi

	msg i "${SERVICE} ${LEVEL0} ${LEVEL1} ${LEVEL2} ${LEVEL3} ${LEVEL4} ${LEVEL5} ${LEVEL6}"
	if [ -x "/etc/init.d/${SERVICE}" ]; then
		for (( count = 0 ; count <= 6 ; count++ )); do
			eval "CURRENT=\$LEVEL${count}"
			if [ -z "${CURRENT}" ]; then continue; fi
			LEVEL=${CURRENT::1}
			ONOFF=${CURRENT:2}
			if [ ${LEVEL} -ge 0 -a ${LEVEL} -le 6 ]; then
				if [ ${ONOFF} == "on" -o ${ONOFF} == "off" ]; then
					chkconfig --level ${LEVEL} ${SERVICE} ${ONOFF}
				else
					msg w "switch must be \"on\" or \"off\""
				fi
			else
				msg w "level must be in 0..6"
			fi
		done
	else
		 msg w "service \"${SERVICE}\" does not installed."
	fi
done