论坛首页 综合技术论坛

读ejabberdctl学先进科技

浏览 3935 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-04-02  
最完美的shell和erlang控制的结合,比其他系统要优雅不知道多少,仔细研究你会有很大的收获的,至少可以列出10个卖点.

cat ejabberdctl.template


#!/bin/sh

# define default configuration
POLL=true
SMP=auto
ERL_MAX_PORTS=32000
ERL_PROCESSES=250000
ERL_MAX_ETS_TABLES=1400

# define default environment variables
NODE=ejabberd
HOST=localhost
ERLANG_NODE=$NODE@$HOST
ERL=@erl@
ROOTDIR=@rootdir@
EJABBERD_CONFIG_PATH=$ROOTDIR/etc/ejabberd/ejabberd.cfg
LOGS_DIR=$ROOTDIR/var/log/ejabberd/
EJABBERD_DB=$ROOTDIR/var/lib/ejabberd/db/$NODE

# read custom configuration
CONFIG=$ROOTDIR/etc/ejabberd/ejabberdctl.cfg
[ -f "$CONFIG" ] && . "$CONFIG"

# parse command line parameters
ARGS=
while [ $# -ne 0 ] ; do
    PARAM=$1
    shift
    case $PARAM in
        --) break ;;
        --node) ERLANG_NODE=$1; shift ;;
        --config) EJABBERD_CONFIG_PATH=$1 ; shift ;;
        --ctl-config) CONFIG=$1 ; shift ;;
        --logs) LOGS_DIR=$1 ; shift ;;
        --spool) EJABBERD_DB=$1 ; shift ;;
        *) ARGS="$ARGS $PARAM" ;;
    esac
done

NAME=-name
[ "$ERLANG_NODE" = "${ERLANG_NODE%.*}" ] && NAME=-sname

ERLANG_OPTS="+K $POLL -smp $SMP +P $ERL_PROCESSES"

# define additional environment variables
EJABBERD_EBIN=$ROOTDIR/var/lib/ejabberd/ebin
EJABBERD_MSGS_PATH=$ROOTDIR/var/lib/ejabberd/priv/msgs
EJABBERD_SO_PATH=$ROOTDIR/var/lib/ejabberd/priv/lib
EJABBERD_BIN_PATH=$ROOTDIR/var/lib/ejabberd/priv/bin
EJABBERD_LOG_PATH=$LOGS_DIR/ejabberd.log
SASL_LOG_PATH=$LOGS_DIR/sasl.log
DATETIME=`date "+%Y%m%d-%H%M%S"`
ERL_CRASH_DUMP=$LOGS_DIR/erl_crash_$DATETIME.dump
ERL_INETRC=$ROOTDIR/etc/ejabberd/inetrc
HOME=$ROOTDIR/var/lib/ejabberd

# export global variables
export EJABBERD_CONFIG_PATH
export EJABBERD_MSGS_PATH
export EJABBERD_LOG_PATH
export EJABBERD_SO_PATH
export EJABBERD_BIN_PATH
export ERL_CRASH_DUMP
export ERL_INETRC
export ERL_MAX_PORTS
export ERL_MAX_ETS_TABLES
export HOME

[ -d $EJABBERD_DB ] || mkdir -p $EJABBERD_DB
[ -d $LOGS_DIR ] || mkdir -p $LOGS_DIR

# Compatibility in ZSH
#setopt shwordsplit 2>/dev/null

# start server
start ()
{
    $ERL \
      $NAME $ERLANG_NODE \
      -noinput -detached \
      -pa $EJABBERD_EBIN \
      -mnesia dir "\"$EJABBERD_DB\"" \
      -s ejabberd \
      -sasl sasl_error_logger \{file,\"$SASL_LOG_PATH\"\} \
      $ERLANG_OPTS $ARGS "$@"
}

# attach to server
debug ()
{
    echo "--------------------------------------------------------------------"
    echo ""
    echo "IMPORTANT: we will attempt to attach an INTERACTIVE shell"
    echo "to an already running ejabberd node."
    echo "If an ERROR is printed, it means the connection was not succesfull."
    echo "You can interact with the ejabberd node if you know how to use it."
    echo "Please be extremely cautious with your actions,"
    echo "and exit immediately if you are not completely sure."
    echo ""
    echo "To detach this shell from ejabberd, press:"
    echo "  control+c, control+c"
    echo ""
    echo "--------------------------------------------------------------------"
    echo "Press any key to continue"
    read foo
    echo ""
    $ERL \
      $NAME ${NODE}debug \
      -remsh $ERLANG_NODE \
      $ERLANG_OPTS $ARGS "$@"
}

# start interactive server
live ()
{
    echo "--------------------------------------------------------------------"
    echo ""
    echo "IMPORTANT: ejabberd is going to start in LIVE (interactive) mode."
    echo "All log messages will be shown in the command shell."
    echo "You can interact with the ejabberd node if you know how to use it."
    echo "Please be extremely cautious with your actions,"
    echo "and exit immediately if you are not completely sure."
    echo ""
    echo "To exit this LIVE mode and stop ejabberd, press:"
    echo "  q().  and press the Enter key"
    echo ""
    echo "--------------------------------------------------------------------"
    echo "Press any key to continue"
    read foo
    echo ""
    $ERL \
      $NAME $ERLANG_NODE \
      -pa $EJABBERD_EBIN \
      -mnesia dir "\"$EJABBERD_DB\"" \
      -s ejabberd \
      $ERLANG_OPTS $ARGS "$@"
}

# common control function
ctl ()
{
    $ERL \
      $NAME ejabberdctl \
      -noinput \
      -pa $EJABBERD_EBIN \
      -s ejabberd_ctl -extra $ERLANG_NODE $@
    result=$?
    case $result in
    0) :;;
    *)
        echo ""
        echo "Commands to start an ejabberd node:"
        echo "  start  Start an ejabberd node in server mode"
        echo "  debug  Attach an interactive Erlang shell to a running ejabberd node"
        echo "  live   Start an ejabberd node in live (interactive) mode"
        echo ""
        echo "Optional parameters when starting an ejabberd node:"
        echo "  --config file      Config file of ejabberd:    $EJABBERD_CONFIG_PATH"
        echo "  --ctl-config file  Config file of ejabberdctl: $CONFIG"
        echo "  --logs dir         Directory for logs:         $LOGS_DIR"
        echo "  --spool dir        Database spool dir:         $EJABBERD_DB"
        echo "  --node nodename    ejabberd node name:         $ERLANG_NODE"
        echo "";;
    esac
    return $result
}

# display ctl usage
usage ()
{
    ctl
    exit
}

case $ARGS in
    ' start') start;;
    ' debug') debug;;
    ' live') live;;
    *) ctl $ARGS;;
esac


cat ejabberdctl.cfg.example


#
# In this file you can configure options that are passed by ejabberdctl
# to the erlang runtime system when starting ejabberd
#

# POLL: Kernel polling ([true|false])
#
# The kernel polling option requires support in the kernel.
# Additionaly, you need to enable this feature while compiling Erlang.
#
# Default: true
#
#POLL=true

# SMP: SMP support ([enable|auto|disable])
#
# Explanation in Erlang/OTP documentation:
# enable: starts the Erlang runtime system with SMP support enabled.
#   This may fail if no runtime system with SMP support is available.
# auto: starts the Erlang runtime system with SMP support enabled if it
#   is available and more than one logical processor are detected.
# disable: starts a runtime system without SMP support.
#
# Default: auto
#
#SMP=auto

# ERL_MAX_PORTS: Maximum number of simultaneously open Erlang ports
#
# ejabberd consumes two or three ports for every connection, either
# from a client or from another Jabber server. So take this into
# account when setting this limit.
#
# Default: 32000
# Maximum: 268435456
#
#ERL_MAX_PORTS=32000

# PROCESSES: Maximum number of Erlang processes
#
# Erlang consumes a lot of lightweight processes. If there is a lot of activity
# on ejabberd so that the maximum number of proccesses is reached, people will
# experiment greater latency times. As these processes are implemented in
# Erlang, and therefore not related to the operating system processes, you do
# not have to worry about allowing a huge number of them.
#
# Default: 250000
# Maximum: 268435456
#
#PROCESSES=250000

# ERL_MAX_ETS_TABLES: Maximum number of ETS and Mnesia tables
#
# The number of concurrent ETS and Mnesia tables is limited. When the limit is
# reached, errors will appear in the logs:
#   ** Too many db tables **
# You can safely increase this limit when starting ejabberd. It impacts memory
# consumption but the difference will be quite small.
#
# Default: 1400
#
#ERL_MAX_ETS_TABLES=1400

# The next variable allows to explicitly specify erlang node for ejabberd
# It can be given in different formats:
# ERLANG_NODE=ejabberd
#   Lets erlang add hostname to the node (ejabberd uses short name in this case)
# ERLANG_NODE=ejabberd@hostname
#   Erlang uses node name as is (so make sure that hostname is a real
#   machine hostname or you'll not be able to control ejabberd)
# ERLANG_NODE=ejabberd@hostname.domainname
#   The same as previous, but erlang will use long hostname
#   (see erl (1) manual for details)
#
# Default: ejabberd
#
#ERLANG_NODE=ejabberd

cat ejabberd_ctl.erl
%%%----------------------------------------------------------------------
%%% File    : ejabberd_ctl.erl
%%% Author  : Alexey Shchepin <alexey@process-one.net>
%%% Purpose : Ejabberd admin tool
%%% Created : 11 Jan 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2009   ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License
%%% along with this program; if not, write to the Free Software
%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
%%% 02111-1307 USA
%%%
%%%----------------------------------------------------------------------

-module(ejabberd_ctl).
-author('alexey@process-one.net').

-export([start/0,
         init/0,
         process/1,
         dump_to_textfile/1,
         register_commands/3,
         register_commands/4,
         unregister_commands/3,
         unregister_commands/4]).

-include("ejabberd_ctl.hrl").
-include("ejabberd.hrl").

start() ->
    case init:get_plain_arguments() of
        [SNode | Args] ->
            SNode1 = case string:tokens(SNode, "@") of
                [_Node, _Server] ->
                    SNode;
                _ ->
                    case net_kernel:longnames() of
                         true ->
                             SNode ++ "@" ++ inet_db:gethostname() ++
                                      "." ++ inet_db:res_option(domain);
                         false ->
                             SNode ++ "@" ++ inet_db:gethostname();
                         _ ->
                             SNode
                     end
            end,
            Node = list_to_atom(SNode1),
            Status = case rpc:call(Node, ?MODULE, process, [Args]) of
                         {badrpc, Reason} ->
                             ?PRINT("RPC failed on the node ~p: ~p~n",
                                       [Node, Reason]),
                             ?STATUS_BADRPC;
                         S ->
                             S
                     end,
            halt(Status);
        _ ->
            print_usage(),
            halt(?STATUS_USAGE)
    end.

init() ->
    ets:new(ejabberd_ctl_cmds, [named_table, set, public]),
    ets:new(ejabberd_ctl_host_cmds, [named_table, set, public]).


process(["status"]) ->
    {InternalStatus, ProvidedStatus} = init:get_status(),
    ?PRINT("Node ~p is ~p. Status: ~p~n",
              [node(), InternalStatus, ProvidedStatus]),
    case lists:keysearch(ejabberd, 1, application:which_applications()) of
        false ->
            ?PRINT("ejabberd is not running~n", []),
            ?STATUS_ERROR;
        {value,_Version} ->
            ?PRINT("ejabberd is running~n", []),
            ?STATUS_SUCCESS
    end;

process(["stop"]) ->
    init:stop(),
    ?STATUS_SUCCESS;

process(["restart"]) ->
    init:restart(),
    ?STATUS_SUCCESS;

process(["reopen-log"]) ->
    ejabberd_logger_h:reopen_log(),
    case application:get_env(sasl,sasl_error_logger) of
        {ok, {file, SASLfile}} ->
            error_logger:delete_report_handler(sasl_report_file_h),
            ejabberd_logger_h:rotate_log(SASLfile),
            error_logger:add_report_handler(sasl_report_file_h,
                                            {SASLfile, get_sasl_error_logger_type()});
        _ -> false
    end,
    ?STATUS_SUCCESS;

process(["register", User, Server, Password]) ->
    case ejabberd_auth:try_register(User, Server, Password) of
        {atomic, ok} ->
            ?STATUS_SUCCESS;
        {atomic, exists} ->
            ?PRINT("User ~p already registered at node ~p~n",
                      [User ++ "@" ++ Server, node()]),
            ?STATUS_ERROR;
        {error, Reason} ->
            ?PRINT("Can't register user ~p at node ~p: ~p~n",
                      [User ++ "@" ++ Server, node(), Reason]),
            ?STATUS_ERROR
    end;

process(["unregister", User, Server]) ->
    case ejabberd_auth:remove_user(User, Server) of
        {error, Reason} ->
            ?PRINT("Can't unregister user ~p at node ~p: ~p~n",
                      [User ++ "@" ++ Server, node(), Reason]),
            ?STATUS_ERROR;
        _ ->
            ?STATUS_SUCCESS
    end;

process(["backup", Path]) ->
    case mnesia:backup(Path) of
        ok ->
            ?STATUS_SUCCESS;
        {error, Reason} ->
            ?PRINT("Can't store backup in ~p at node ~p: ~p~n",
                      [filename:absname(Path), node(), Reason]),
            ?STATUS_ERROR
    end;

process(["dump", Path]) ->
    case dump_to_textfile(Path) of
        ok ->
            ?STATUS_SUCCESS;
        {error, Reason} ->
            ?PRINT("Can't store dump in ~p at node ~p: ~p~n",
                      [filename:absname(Path), node(), Reason]),
            ?STATUS_ERROR
    end;

process(["load", Path]) ->
    case mnesia:load_textfile(Path) of
        {atomic, ok} ->
            ?STATUS_SUCCESS;
        {error, Reason} ->
            ?PRINT("Can't load dump in ~p at node ~p: ~p~n",
                      [filename:absname(Path), node(), Reason]),
            ?STATUS_ERROR
    end;

process(["restore", Path]) ->
    case ejabberd_admin:restore(Path) of
        {atomic, _} ->
            ?STATUS_SUCCESS;
        {error, Reason} ->
            ?PRINT("Can't restore backup from ~p at node ~p: ~p~n",
                      [filename:absname(Path), node(), Reason]),
            ?STATUS_ERROR;
        {aborted,{no_exists,Table}} ->
            ?PRINT("Can't restore backup from ~p at node ~p: Table ~p does not exist.~n",
                      [filename:absname(Path), node(), Table]),
            ?STATUS_ERROR;
        {aborted,enoent} ->
            ?PRINT("Can't restore backup from ~p at node ~p: File not found.~n",
                      [filename:absname(Path), node()]),
            ?STATUS_ERROR
    end;

process(["install-fallback", Path]) ->
    case mnesia:install_fallback(Path) of
        ok ->
            ?STATUS_SUCCESS;
        {error, Reason} ->
            ?PRINT("Can't install fallback from ~p at node ~p: ~p~n",
                      [filename:absname(Path), node(), Reason]),
            ?STATUS_ERROR
    end;

process(["import-file", Path]) ->
    case jd2ejd:import_file(Path) of
        ok ->
            ?STATUS_SUCCESS;
        {error, Reason} ->
            ?PRINT("Can't import jabberd 1.4 spool file ~p at node ~p: ~p~n",
                      [filename:absname(Path), node(), Reason]),
            ?STATUS_ERROR
    end;

process(["import-dir", Path]) ->
    case jd2ejd:import_dir(Path) of
        ok ->
            ?STATUS_SUCCESS;
        {error, Reason} ->
            ?PRINT("Can't import jabberd 1.4 spool dir ~p at node ~p: ~p~n",
                      [filename:absname(Path), node(), Reason]),
            ?STATUS_ERROR
    end;

process(["delete-expired-messages"]) ->
    mod_offline:remove_expired_messages(),
    ?STATUS_SUCCESS;

process(["mnesia"]) ->
    ?PRINT("~p~n", [mnesia:system_info(all)]),
    ?STATUS_SUCCESS;

process(["mnesia", "info"]) ->
    mnesia:info(),
    ?STATUS_SUCCESS;

process(["mnesia", Arg]) when is_list(Arg) ->
    case catch mnesia:system_info(list_to_atom(Arg)) of
        {'EXIT', Error} -> ?PRINT("Error: ~p~n", [Error]);
        Return -> ?PRINT("~p~n", [Return])
    end,
    ?STATUS_SUCCESS;

process(["delete-old-messages", Days]) ->
    case catch list_to_integer(Days) of
        {'EXIT',{Reason, _Stack}} ->
            ?PRINT("Can't delete old messages (~p). Please pass an integer as parameter.~n",
                      [Reason]),
            ?STATUS_ERROR;
        Integer when Integer >= 0 ->
            {atomic, _} = mod_offline:remove_old_messages(Integer),
            ?PRINT("Removed messages older than ~s days~n", [Days]),
            ?STATUS_SUCCESS;
        _Integer ->
            ?PRINT("Can't delete old messages. Please pass a positive integer as parameter.~n", []),
            ?STATUS_ERROR
    end;

process(["vhost", H | Args]) ->
    case jlib:nameprep(H) of
        false ->
            ?PRINT("Bad hostname: ~p~n", [H]),
            ?STATUS_ERROR;
        Host ->
            case ejabberd_hooks:run_fold(
                   ejabberd_ctl_process, Host, false, [Host, Args]) of
                false ->
                    print_vhost_usage(Host),
                    ?STATUS_USAGE;
                Status ->
                    Status
            end
    end;

process(Args) ->
    case ejabberd_hooks:run_fold(ejabberd_ctl_process, false, [Args]) of
        false ->
            print_usage(),
            ?STATUS_USAGE;
        Status ->
            Status
    end.


print_usage() ->
    CmdDescs =
        [{"status", "get ejabberd status"},
         {"stop", "stop ejabberd"},
         {"restart", "restart ejabberd"},
         {"reopen-log", "reopen log file"},
         {"register user server password", "register a user"},
         {"unregister user server", "unregister a user"},
         {"backup file", "store a database backup to file"},
         {"restore file", "restore a database backup from file"},
         {"install-fallback file", "install a database fallback from file"},
         {"dump file", "dump a database to a text file"},
         {"load file", "restore a database from a text file"},
         {"import-file file", "import user data from jabberd 1.4 spool file"},
         {"import-dir dir", "import user data from jabberd 1.4 spool directory"},
         {"delete-expired-messages", "delete expired offline messages from database"},
         {"delete-old-messages n", "delete offline messages older than n days from database"},
         {"mnesia [info]", "show information of Mnesia system"},
         {"vhost host ...", "execute host-specific commands"}] ++
        ets:tab2list(ejabberd_ctl_cmds),
    MaxCmdLen =
        lists:max(lists:map(
                    fun({Cmd, _Desc}) ->
                            length(Cmd)
                    end, CmdDescs)),
    NewLine = io_lib:format("~n", []),
    FmtCmdDescs =
        lists:map(
          fun({Cmd, Desc}) ->
                  ["  ", Cmd, string:chars($\s, MaxCmdLen - length(Cmd) + 2),
                   Desc, NewLine]
          end, CmdDescs),
    ?PRINT(
      "Usage: ejabberdctl [--node nodename] command [options]~n"
      "~n"
      "Available commands in this ejabberd node:~n"
      ++ FmtCmdDescs ++
      "~n"
      "Examples:~n"
      "  ejabberdctl restart~n"
      "  ejabberdctl --node ejabberd@host restart~n"
      "  ejabberdctl vhost jabber.example.org ...~n",
     []).

print_vhost_usage(Host) ->
    CmdDescs =
        ets:select(ejabberd_ctl_host_cmds,
                   [{{{Host, '$1'}, '$2'}, [], [{{'$1', '$2'}}]}]),
    MaxCmdLen =
        if
            CmdDescs == [] ->
                0;
            true ->
                lists:max(lists:map(
                            fun({Cmd, _Desc}) ->
                                    length(Cmd)
                            end, CmdDescs))
        end,
    NewLine = io_lib:format("~n", []),
    FmtCmdDescs =
        lists:map(
          fun({Cmd, Desc}) ->
                  ["  ", Cmd, string:chars($\s, MaxCmdLen - length(Cmd) + 2),
                   Desc, NewLine]
          end, CmdDescs),
    ?PRINT(
      "Usage: ejabberdctl [--node nodename] vhost hostname command [options]~n"
      "~n"
      "Available commands in this ejabberd node and this vhost:~n"
      ++ FmtCmdDescs ++
      "~n"
      "Examples:~n"
      "  ejabberdctl vhost "++Host++" registered-users~n",
     []).

register_commands(CmdDescs, Module, Function) ->
    ets:insert(ejabberd_ctl_cmds, CmdDescs),
    ejabberd_hooks:add(ejabberd_ctl_process,
                       Module, Function, 50),
    ok.

register_commands(Host, CmdDescs, Module, Function) ->
    ets:insert(ejabberd_ctl_host_cmds,
               [{{Host, Cmd}, Desc} || {Cmd, Desc} <- CmdDescs]),
    ejabberd_hooks:add(ejabberd_ctl_process, Host,
                       Module, Function, 50),
    ok.

unregister_commands(CmdDescs, Module, Function) ->
    lists:foreach(fun(CmdDesc) ->
                          ets:delete_object(ejabberd_ctl_cmds, CmdDesc)
                  end, CmdDescs),
    ejabberd_hooks:delete(ejabberd_ctl_process,
                          Module, Function, 50),
    ok.

unregister_commands(Host, CmdDescs, Module, Function) ->
    lists:foreach(fun({Cmd, Desc}) ->
                          ets:delete_object(ejabberd_ctl_host_cmds,
                                            {{Host, Cmd}, Desc})
                  end, CmdDescs),
    ejabberd_hooks:delete(ejabberd_ctl_process,
                          Module, Function, 50),
    ok.

dump_to_textfile(File) ->
    dump_to_textfile(mnesia:system_info(is_running), file:open(File, write)).
dump_to_textfile(yes, {ok, F}) ->
    Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
    Tabs = lists:filter(
             fun(T) ->
                     case mnesia:table_info(T, storage_type) of
                         disc_copies -> true;
                         disc_only_copies -> true;
                         _ -> false
                     end
             end, Tabs1),
    Defs = lists:map(
             fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
                            {attributes, mnesia:table_info(T, attributes)}]}
             end,
             Tabs),
    io:format(F, "~p.~n", [{tables, Defs}]),
    lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
    file:close(F);
dump_to_textfile(_, {ok, F}) ->
    file:close(F),
    {error, mnesia_not_running};
dump_to_textfile(_, {error, Reason}) ->
    {error, Reason}.


dump_tab(F, T) ->
    W = mnesia:table_info(T, wild_pattern),
    {atomic,All} = mnesia:transaction(
                     fun() -> mnesia:match_object(T, W, read) end),
    lists:foreach(
      fun(Term) -> io:format(F,"~p.~n", [setelement(1, Term, T)]) end, All).

%% Function copied from Erlang/OTP lib/sasl/src/sasl.erl which doesn't export it
get_sasl_error_logger_type () ->
    case application:get_env (sasl, errlog_type) of
        {ok, error} -> error;
        {ok, progress} -> progress;
        {ok, all} -> all;
        {ok, Bad} -> exit ({bad_config, {sasl, {errlog_type, Bad}}});
        _ -> all
    end.
   发表时间:2009-04-09  
呵呵,真不错。
可以做为项目模板使用。
哦。刚才看到 erl -remsh, 试用了一下,还是很神奇的。
出了CTRL + G 连接远程的node,这个也不错。。
赞!
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics