1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

王国アドベントカレンダーAdvent Calendar 2023

Day 7

EC2に建てたマイクラのサーバーをDiscordから起動する

Posted at

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上に上げていたので、草がいっぱい生えて綺麗でした。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?