Inital commit

This commit is contained in:
2026-06-24 13:44:39 -04:00
commit f098d5f170
15 changed files with 471 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
mc-tool

30
config/config.go Normal file
View File

@@ -0,0 +1,30 @@
package config
import (
"text/template"
)
var Cfg Config = Config{
Name: "minecraft-server",
Version: "1.21.1",
Type: "VANILLA",
Ram: "2G",
MOTD: "This server was created using mc-tool",
RestartPolicy: "never",
Ports: []string{"25565:25565"},
}
var Data Resources
type Config struct {
Name string
Version string
Type string
Ram string
MOTD string
RestartPolicy string
Ports []string
}
type Resources struct {
DockerCompose *template.Template
}

67
docker/docker.go Normal file
View File

@@ -0,0 +1,67 @@
package docker
import (
"fmt"
"log"
"strings"
)
type Port struct {
In string
Out string
}
type ServerType int
const (
FORGE ServerType = iota
FABRIC
NEOFORGE
VANILLA
)
func (p Port) String() string {
return fmt.Sprintf("%s:%s", p.In, p.Out)
}
func (t ServerType) String() string {
switch t {
case FORGE:
return "FORGE"
case NEOFORGE:
return "NEOFORGE"
case VANILLA:
return "VANILLA"
}
log.Panicln("unreachable")
return ""
}
func PortFromString(port string) Port {
parts := strings.Split(port, ":")
return Port{
In: parts[0],
Out: parts[1],
}
}
func PortsFromStrings(ports []string) (r []Port) {
for _, port := range ports {
r = append(r, PortFromString(port))
}
return
}
func TypeFromString(in string) ServerType {
switch in {
case "FORGE":
return FORGE
case "NEOFORGE":
return NEOFORGE
case "VANILLA":
return VANILLA
default:
log.Fatalf("unknown server type \"%s\"", in)
}
return VANILLA
}

39
docker/docker_compose.go Normal file
View File

@@ -0,0 +1,39 @@
package docker
import (
"log"
"mc-tool/config"
"strings"
)
type Dockercompose struct {
Name string
Ports []Port
Version string
Type string
Ram string
MOTD string
RestartPolicy string
}
func NewCompose(name string) Dockercompose {
return Dockercompose{
Name: name,
Ports: PortsFromStrings(config.Cfg.Ports),
Version: config.Cfg.Version,
Type: TypeFromString(config.Cfg.Type).String(),
Ram: config.Cfg.Ram,
MOTD: config.Cfg.MOTD,
RestartPolicy: config.Cfg.RestartPolicy,
}
}
func (dc Dockercompose) String() string {
b := new(strings.Builder)
err := config.Data.DockerCompose.ExecuteTemplate(b, "compose", dc)
if err != nil {
log.Fatal(err)
}
return b.String()
}

9
go.mod Normal file
View File

@@ -0,0 +1,9 @@
module mc-tool
go 1.26.4
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.10 // indirect
)

11
go.sum Normal file
View File

@@ -0,0 +1,11 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

90
main.go Normal file
View File

@@ -0,0 +1,90 @@
package main
import (
_ "embed"
"log"
"mc-tool/config"
"mc-tool/server"
"os"
"text/template"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "mc-tool [command]",
Short: "Minecraft Management Tool",
Long: "mc-tool simplifies common server administration actions",
Args: cobra.MinimumNArgs(1),
}
var createCmd = &cobra.Command{
Use: "create",
Short: "Creates a new minecraft server",
Run: server.New,
Args: cobra.ExactArgs(1),
}
var startCmd = &cobra.Command{
Use: "start",
Short: "Start the minecraft server",
Run: server.Start,
Args: cobra.ExactArgs(1),
}
var stopCmd = &cobra.Command{
Use: "stop",
Short: "Stop the minecraft server",
Run: server.Stop,
Args: cobra.ExactArgs(1),
}
var restartCmd = &cobra.Command{
Use: "restart",
Short: "Restart the minecraft server",
Run: server.Restart,
Args: cobra.ExactArgs(1),
}
var shellCmd = &cobra.Command{
Use: "shell",
Short: "Access RCON shell",
Run: server.Shell,
Args: cobra.ExactArgs(1),
}
//go:embed templates/docker-compose.yml
var dockerCompose string
func init() {
createCmd.Flags().StringVarP(&config.Cfg.Name, "name", "n", "minecraft-server", "set the name of the server")
createCmd.Flags().StringVarP(&config.Cfg.Version, "version", "v", "1.21.1", "set the version of the server")
createCmd.Flags().StringVarP(&config.Cfg.Type, "type", "t", "VANILLA", "set the type of the server")
createCmd.Flags().StringVarP(&config.Cfg.Ram, "ram", "r", "2G", "set the memory limit of the server")
createCmd.Flags().StringVarP(&config.Cfg.MOTD, "motd", "m", "This server was created using mc-tool", "set the message of the day for the server")
createCmd.Flags().StringVarP(&config.Cfg.RestartPolicy, "restart", "R", "never", "what to do when the server fails or host turns off")
createCmd.Flags().FuncP("port", "p", "set additional ports for the container to expose", func(s string) error {
config.Cfg.Ports = append(config.Cfg.Ports, s)
return nil
})
rootCmd.AddCommand(createCmd)
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(stopCmd)
rootCmd.AddCommand(restartCmd)
rootCmd.AddCommand(shellCmd)
t, err := template.New("compose").Parse(dockerCompose)
if err != nil {
log.Fatal(err)
}
config.Data.DockerCompose = t
}
func main() {
log.SetFlags(0)
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
os.Exit(1)
}
}

37
server/container.go Normal file
View File

@@ -0,0 +1,37 @@
package server
import (
"bufio"
"log"
"os"
"strings"
)
func getServerName(dir string) string {
if err := os.Chdir(dir); err != nil {
log.Fatal(err)
}
file, err := os.Open("docker-compose.yml")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
name := dir
for scanner.Scan() {
txt := scanner.Text()
txt = strings.TrimSpace(txt)
s, found := strings.CutPrefix(txt, "container_name: ")
if found {
name = s
}
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
return name
}

47
server/new.go Normal file
View File

@@ -0,0 +1,47 @@
package server
import (
"log"
"mc-tool/docker"
"os"
"path/filepath"
"github.com/spf13/cobra"
)
func New(cmd *cobra.Command, args []string) {
dir := args[0]
if err := os.Mkdir(dir, 0755); err != nil {
log.Fatal(err)
}
if err := os.Chdir(dir); err != nil {
log.Fatal(err)
}
compose := docker.NewCompose(dir)
if compose.Type != docker.VANILLA.String() {
if err := os.MkdirAll("data/mods", 0755); err != nil {
log.Fatal(err)
}
if err := os.MkdirAll("data/config", 0755); err != nil {
log.Fatal(err)
}
}
if err := os.MkdirAll("data/world", 0755); err != nil {
log.Fatal(err)
}
if err := writeFile("docker-compose.yml", compose.String()); err != nil {
log.Fatal(err)
}
}
func writeFile(path string, data string) error {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
return os.WriteFile(path, []byte(data), 0644)
}

21
server/restart.go Normal file
View File

@@ -0,0 +1,21 @@
package server
import (
"log"
"os"
"os/exec"
"github.com/spf13/cobra"
)
func Restart(cmd *cobra.Command, args []string) {
dir := args[0]
if err := os.Chdir(dir); err != nil {
log.Fatal(err)
}
start_cmd := exec.Command("docker", "compose", "restart")
if err := start_cmd.Run(); err != nil {
log.Fatal(err)
}
}

31
server/shell.go Normal file
View File

@@ -0,0 +1,31 @@
package server
import (
"log"
"os/exec"
"strings"
"github.com/spf13/cobra"
)
func Shell(cmd *cobra.Command, args []string) {
dir := args[0]
name := getServerName(dir)
start_cmd := exec.Command("docker", "exec", name, "rcon-cli")
if err := start_cmd.Run(); err != nil {
log.Fatal(err)
}
}
func Say(cmd *cobra.Command, args []string) {
dir := args[0]
name := getServerName(dir)
start_cmd := exec.Command("docker", "exec", name, "rcon-cli", strings.Join(args[1:], " "))
if err := start_cmd.Run(); err != nil {
log.Fatal(err)
}
}

21
server/start.go Normal file
View File

@@ -0,0 +1,21 @@
package server
import (
"log"
"os"
"os/exec"
"github.com/spf13/cobra"
)
func Start(cmd *cobra.Command, args []string) {
dir := args[0]
if err := os.Chdir(dir); err != nil {
log.Fatal(err)
}
start_cmd := exec.Command("docker", "compose", "up", "-d")
if err := start_cmd.Run(); err != nil {
log.Fatal(err)
}
}

28
server/status.go Normal file
View File

@@ -0,0 +1,28 @@
package server
import (
"bytes"
"fmt"
"log"
"os/exec"
"github.com/spf13/cobra"
)
func Status(cmd *cobra.Command, args []string) {
dir := args[0]
name := getServerName(dir)
start_cmd := exec.Command("docker", "exec", name, "rcon-cli", "list")
var outb, errb bytes.Buffer
start_cmd.Stdout = &outb
start_cmd.Stderr = &errb
if err := start_cmd.Run(); err != nil {
log.Fatal(err)
}
fmt.Println(outb.String())
}

21
server/stop.go Normal file
View File

@@ -0,0 +1,21 @@
package server
import (
"log"
"os"
"os/exec"
"github.com/spf13/cobra"
)
func Stop(cmd *cobra.Command, args []string) {
dir := args[0]
if err := os.Chdir(dir); err != nil {
log.Fatal(err)
}
start_cmd := exec.Command("docker", "compose", "down")
if err := start_cmd.Run(); err != nil {
log.Fatal(err)
}
}

View File

@@ -0,0 +1,18 @@
service:
mc:
image: itzg/minecraft-server:latest
container_name: {{.Name}}
tty: true
stdin_open: true
ports:
{{range .Ports}}- "{{.In}}:{{.Out}}"
{{end}}
environment:
EULA: "TRUE"
VERSION: "{{.Version}}"
TYPE: {{.Type}}
MEMORY: "{{.Ram}}"
MOTD: "{{.MOTD}}"
volumes:
- ./data:/data
restart: "{{.RestartPolicy}}"