EC2に建てたマイクラのサーバーをDiscordから起動する
だいぶ前にマイクラにハマっていた時がありました。
その時に一緒に遊んでいた仲間内で自由にサーバーの起動ができるように、DiccordからEC2上に建てたマイクラのサーバーを起動するBotを作成したので、作り方を簡単に紹介します。
構成
- EC2(マイクラサーバー)
- Lambda(サーバー側)
- DiscordBot(クライアント側)
DiscordBotからLambdaにリクエストを飛ばし、LambdaからEC2を起動します。
EC2
起動スクリプトです。
これをEC2起動時に自動で実行するようにします。
マイクラ内でstopコマンドを叩くことでマイクラサーバーが終了し、そのままEC2を終了させるリクエストをLambdaに投げます。
ついでに、DiscordのWebHookでサーバーが終了したときに通知するようにします。
java -Xms$XMS_SIZE -Xmx$XMX_SIZE -jar {マイクラサーバーのJarファイル} -nogui
curl -X POST {DiscordのWebHookURL} -H 'Content-Type: application/json' --data '{"content": "サーバーを終了します"}'
curl -X POST {LambdaのリクエストURL} -H 'Content-Type: application/json' -H 'x-api-key: {LambdaのAPIキー}' --data '{"action": "stop"}'
Lambda
LambdaからEC2を操作するコードです。
リクエストボディーからリクエストの種類を取得し、起動・停止します。
package main
import (
"encoding/json"
"log"
"os"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
)
type Action string
const (
ACTION_STOP Action = "stop"
ACTION_START Action = "start"
)
type Request struct {
Action Action `json:"action"`
}
type Response struct {
Result int `json:"result"`
Message string `json:"message"`
}
var instanceId string
const (
layout = "2006-01-02"
region = "ap-northeast-1"
)
func init() {
instanceId = os.Getenv("TARGET_INSTANCE_ID")
}
func startInstance(ec2Client *ec2.EC2, instance string) error {
params := &ec2.StartInstancesInput{
InstanceIds: []*string{
&instance,
},
}
_, err := ec2Client.StartInstances(params)
return err
}
func stopInstance(ec2Client *ec2.EC2, instance string) error {
params := &ec2.StopInstancesInput{
InstanceIds: []*string{
&instance,
},
}
_, err := ec2Client.StopInstances(params)
return err
}
func proc(request Request) Response {
session := session.Must(session.NewSession())
svc := ec2.New(session, aws.NewConfig().WithRegion(region))
var (
err error
message string
result int
)
switch request.Action {
case ACTION_STOP:
err = stopInstance(svc, instanceId)
case ACTION_START:
err = startInstance(svc, instanceId)
default:
result = 1
message = "undefined action: " + string(request.Action)
}
if err != nil {
result = -1
message = err.Error()
}
return Response{
Result: result,
Message: message,
}
}
func handler(requestSource events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
log.Println(requestSource.Body)
var request Request
json.Unmarshal([]byte(requestSource.Body), &request)
response := proc(request)
bytes, _ := json.Marshal(response)
return events.APIGatewayProxyResponse{
Body: string(bytes),
StatusCode: 200,
}, nil
}
func main() {
lambda.Start(handler)
}
DiscordBot
全体を乗せると長いので一部を抜粋しています。
*start
という特定のメッセージに反応して、LambdaにEC2を起動するようにリクエストします。
func (a *app) startServer() error {
request := StartRequest{
Action: "start",
}
reqData, err := json.Marshal(request)
if err != nil {
return err
}
req, err := http.NewRequest("POST", "{YOUR_LAMBDA_URL}", bytes.NewBuffer(reqData))
if err != nil {
return err
}
req.Header.Set("x-api-key", "{YOUR_LAMBDA_API_KEY}")
req.Header.Set("content-type", "application/json")
client := new(http.Client)
res, err := client.Do(req)
if err != nil {
return err
}
resData, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
var response StartResponse
err = json.Unmarshal(resData, &response)
if err != nil {
return err
}
if response.Result != 0 {
return errors.New(response.Message)
}
return nil
}
func (a *app) messageHnadler(s *discordgo.Session, m *discordgo.MessageCreate) {
switch m.Message.Content {
case "*start":
var message string
err := a.startServer()
if err != nil {
message = "サーバーの起動に失敗しました。\n" + err.Error()
} else {
message = "サーバーの起動に成功しました"
}
a.discord.ChannelMessageSend(m.ChannelID, message)
default:
s.ChannelMessageDelete(m.ChannelID, m.ID)
}
}
おわり
(あまり大きな声では言えないですが)
マイクラにハマっていたときにバックアップデータをGitlab上に上げていたので、草がいっぱい生えて綺麗でした。