Cafe capybara TECH-BLOG

PHPな会社でゴリゴリしてるあかいいぬの技術ブログです

【今から二時間でLinux版マインクラフトサーバのシェルスクリプト叩くWebアプリ作る】 #cafecapy

発端(https://twitter.com/akai_inu/status/264597216218456064)

二時間制作シリーズ(?)第一回はマインクラフトのサーバ(jar/Linux上)をWeb上で制御出来るアプリを作る、です。

ひとまず最初に完成図↓
f:id:expert88:20121103211919p:plain

機能:マインクラフトサーバの状態確認・起動・終了・再起動・強制終了

※この記事では、"Linuxコマンド", "シェルスクリプト", "PHP", "javascript(jQuery)"を利用しています。
CentOSによるサーバです。
シェルスクリプトについてはバグが残っている可能性があります。

1.マインクラフトのサーバを用意する

$ mkdir /var/www/minecraftsv/ #どこでもいいさ
$ cd /var/www/minecraftsv/
$ wget https://s3.amazonaws.com/MinecraftDownload/launcher/minecraft_server.jar
$ java -Xmx128M -Xms1024M -jar minecraft_server.jar nogui # 念のため一度起動して関連ファイルを生成しておく

2.サーバのバックグラウンド動作用 screen を導入する

マインクラフトのサーバはjavaで動いているので、バックグラウンドで動作させるには一工夫必要である。

$ sudo apt-get install screen

特に設定することはないのでとりあえずインストールだけする。

(参考: てりログ minecraftサーバの導入と設定

3.操作用シェルスクリプトを作る

$ vim mcsv.sh

     1  #!/bin/sh
     2  RAM_MIN=64  # Minimum use of Memory
     3  RAM_MAX=512 # Maximum use of Memory
     4  SCREEN_NAME=minecraft_server_web
     5  cd "${0%/*}"
     6  
     7  check_server()
     8  {
     9          a=`screen -ls | grep "${SCREEN_NAME}"`
    10          if [ ${#a} -gt 0 ]
    11          then
    12                  echo "0"
    13          else
    14                  echo "1"
    15          fi
    16  }
    17  IS_SERVER_RUNNING=`check_server`
    18  
    19  # Starting Server Function
    20  start_server()
    21  {
    22          echo "Starting server..."
    23          screen -AmdS ${SCREEN_NAME} java -Xms${RAM_MIN}M -Xmx${RAM_MAX}M -jar minecraft_server.jar nogui
    24          echo "Server started."
    25  }
    26  
    27  # Stopping Server Function
    28  stop_server()
    29  {
    30          echo "Stopping server..."
    31          i=15
    32          
    33          while [ ${i} -ne 0 ]
    34          do
    35                  if test `expr ${i} % 15` -eq 0 -o ${i} -le 10
    36                  then
    37                          screen -S ${SCREEN_NAME} -p 0 -X eval "stuff say\040Server\040will\040stop\040in\040${i}\040seconds.\015"
    38                          echo "Server will stop in ${i} seconds."
    39                  fi
    40                  i=`expr ${i} - 1`
    41                  sleep 1
    42          done
    43          screen -S ${SCREEN_NAME} -p 0 -X eval 'stuff stop\015'
    44  
    45          echo "Wait Saving..."
    46          sleep 10
    47          echo "Server stopped."
    48  }
    49  
    50  case $1 in
    51  start)
    52          if [ ${IS_SERVER_RUNNING} -eq 0 ]
    53          then
    54                  echo "Maybe server is running. Exit shell."
    55          else
    56                  start_server
    57          fi
    58          ;;
    59  restart)
    60          if [ ${IS_SERVER_RUNNING} -eq 0 ]
    61          then
    62                  echo "Restarting server..."
    63                  stop_server
    64                  start_server
    65          else
    66                  echo "Maybe server is not running. Exit shell."
    67          fi
    68          ;;
    69  stop)
    70          if [ ${IS_SERVER_RUNNING} -eq 0 ]
    71          then
    72                  stop_server
    73          else
    74                  echo "Maybe server is not running. Exit shell."
    75          fi
    76          ;;
    77  terminate)
    78          echo "Terminating Server..."
    79          screen -S ${SCREEN_NAME} -X quit
    80          echo "Terminated server."
    81          ;;
    82  status)
    83          if [ ${IS_SERVER_RUNNING} -eq 0 ]
    84          then
    85                  echo "Minecraft server is running."
    86          else
    87                  echo "Minecraft server is not running."
    88          fi
    89          ;;
    90  *)
    91          echo "usage... $0 (start|restart|stop|status)"
    92          ;;
    93  esac
    94  

なんてことはないシェルスクリプト。ただしこれしきに試行錯誤して4時間くらいかかった。

# chmod a+x mcsv.sh
# chown apache:apache mcsv.sh

実行権限付加+apacheユーザとして実行するので権限移譲

(参考: てりログ minecraftサーバの管理(自動リスタート)

4.Web側を作る

まずアクセスするフォアグラウンドのhtmlファイル

     1  <!DOCTYPE html>
     2  <html>
     3  <head>
     4          <meta charset="utf-8" />
     5          <title>Minecraft Web Scriptor</title>
     6          <script src="/jquery-1.8.2.min.js" type="text/javascript"></script>
     7  
     8          <script type="text/javascript">
     9          function setButtonAndStatus(res) {
    10                  var arr = res.split("\n");
    11                  // 最後は空行なので-2
    12                  var s = arr[arr.length - 2].search('not') == -1;
    13                  if(s) {
    14                          $("#button_start").attr('disabled', 'true');
    15                          $("#button_stop").removeAttr('disabled');
    16                          $("#button_restart").removeAttr('disabled');
    17                          $("#status").text("サーバは起動しています");
    18                  } else {
    19                          $("#button_start").removeAttr('disabled');
    20                          $("#button_stop").attr('disabled', 'true');
    21                          $("#button_restart").attr('disabled', 'true');
    22                          $("#status").text("サーバは起動していません");
    23                  }
    24          }
    25          function GetServerStatus() {
    26                  $.post("./exec.php", { command: "status" }, function(res) {
    27                          $("#statustime").text(new Date($.now()).toLocaleString());
    28                          setButtonAndStatus(res);
    29                  }, "text");
    30                  
    31                  window.setTimeout("GetServerStatus()", 5000);
    32          }
    33          $(function(){
    34                  // Start to get ServerStatus
    35                  GetServerStatus();
    36  
    37                  var ajaxWaiting = false;
    38                  $(window).bind("beforeunload", function() {
    39                          console.log(ajaxWaiting);
    40                          if(ajaxWaiting)
    41                                  return "現在サーバの操作中です。ページから離れる場合サーバエラーが発生する場合があります。";
    42                  });
    43  
    44                  $("#button_start").click(function(e) {
    45                          $("#button_start").attr('disabled', 'true');
    46                          $("#warning").text("現在サーバを起動しています...");
    47                          ajaxWaiting = true;
    48                          $.post("exec.php", { command: "start" }, function(res) {
    49                                  $("#response").append(res);
    50                                  setButtonAndStatus(res);
    51                                  $("#warning").text("");
    52                                  ajaxWaiting = false;
    53                          });
    54                  });
    55                  $("#button_stop").click(function(e) {
    56                          $("#button_stop").attr('disabled', 'true');
    57                          $("#button_restart").attr('disabled', 'true');
    58                          $("#warning").text("現在サーバを終了しています...");
    59                          ajaxWaiting = true;
    60                          $.post("exec.php", { command: "stop" }, function(res) {
    61                                  $("#response").append(res);
    62                                  setButtonAndStatus(res);
    63                                  $("#warning").text("");
    64                                  ajaxWaiting = false;
    65                          });
    66                  });
    67                  $("#button_restart").click(function(e) {
    68                          $("#button_stop").attr('disabled', 'true');
    69                          $("#button_restart").attr('disabled', 'true');
    70                          $("#warning").text("現在サーバを終了しています...");
    71                          ajaxWaiting = true;
    72                          $.post("exec.php", { command: "stop" }, function(res) {
    73                                  $("#response").append(res);
    74                                  $("#warning").text("現在サーバを起動しています...");
    75                                  $.post("exec.php", { command: "start" }, function(res) {
    76                                          $("#response").append(res);
    77                                          setButtonAndStatus(res);
    78                                          $("#warning").text("");
    79                                          ajaxWaiting = false;
    80                                  });
    81                          });
    82                  });
    83                  $("#button_terminate").click(function(e) {
    84                          $.post("exec.php", { command: "terminate" }, function(res) {
    85                                  $("#response").append(res);
    86                                  setButtonAndStatus(res);
    87                          });
    88                  });
    89          });
    90          </script>
    91  </head>
    92  <body>
    93  
    94  <h1>Minecraft Web Scriptor</h1>
    95  
    96  <div style="width:500px;margin:15px;color:#FFF;background:#000;padding:5px;border:3px ridge #FFF;">サーバ状態 : 
    97  <span id="status"></span>(@<span id="statustime"></span>)
    98  <pre id="warning" style="font-weight:bold;text-align:center;font-size:150%;color:#FF5555;"></pre></div>
    99  <input type="button" id="button_start" value="サーバを起動する" disabled="true" />
   100  <input type="button" id="button_stop" value="サーバを終了する" disabled="true" />
   101  <input type="button" id="button_restart" value="サーバを再起動する" disabled="true" />
   102  <input type="button" id="button_terminate" value="サーバを強制終了する" />
   103  <br /><br />
   104  <div style="width:500px;border:3px ridge #FFF;margin:15px;padding:5px;color:#FFF;background:#000;">
   105  サーバ応答 : 
   106  <pre id="response"></pre>
   107  </div>
   108  </body>
   109  </html>

最低限しか書いていないので各自補完すべし。

ここでの大事な所は $.post 関数。
jQueryajaxを簡単に行うためのもので、これはphpやcgiにPOSTをパラメータ付きで投げて成功時にcallbackを実行するもの。
失敗時にエラー処理を行うには$.ajax関数を使わなくてはいけなくて面倒なので今回は省略。実際運用する時はつけないといけないかもしれぬ。

次に、シェルスクリプト実行用のphpファイル

     1  <?php
     2  $filepath = '/var/www/minecraftsv/mcsv.sh ';
     3  exec($filepath.$_POST["command"], $arr);
     4  foreach($arr as $a) {
     5          echo "$a\n";
     6  }
     7  
     8  // status以外は追加でstatusを取得
     9  if($_POST["command"] != "status") {
    10          exec($filepath."status", $arrstat);
    11          foreach($arrstat as $a) {
    12                  echo "$a\n";
    13          }
    14  }
    15  ?>

キモはPHPexec コマンド。
apacheユーザとしてサーバ内のシェルスクリプトを実行することが出来る。
第二引数で実行時にechoされた内容を行ごとに配列で取得。

後は.htaccessBasic認証をかける。かけないと誰でもサーバ再起動し放題で #死


ということで、第一弾は2時間どころか7時間もかかってしまった。試行錯誤の時間を縮めたい所である。

記事にするのが面倒になってかなり説明をはしょっている。よって利用する際に何かわからないことがあればコメント欄まで出してほしい。