Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 29 additions & 22 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
.paper/
banned-ips.json
banned-players.json
bukkit.yml
cache/
commands.yml
config/
eula.txt
help.yml
libraries/
logs/
mcrun
ops.json
paper-*.jar
permissions.yml
plugins/
server.properties
spigot.yml
usercache.json
versions/
whitelist.json
world/
# Anchored to the repository root: an unanchored "mcrun" or "config/" would also
# hide new sources under cmd/mcrun/ and internal/config/.
/.paper/
/banned-ips.json
/banned-players.json
/bukkit.yml
/cache/
/commands.yml
/config/
/eula.txt
/help.yml
/libraries/
/logs/
/mcrun
/mcrun.exe
/mods/
/ops.json
/*.jar
/permissions.yml
/plugins/
/server.properties
/spigot.yml
/usercache.json
/versions/
/whitelist.json
/world/

# Names no source file shares, ignored wherever a test server was run
.mcrun-state.json
*.bak
122 changes: 87 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ A cross-platform CLI tool to download and run Minecraft servers with integrated
## Features

- **Cross-platform**: Works on Linux, Windows, and macOS
- **Multiple server types**: Vanilla, Paper, Fabric, Forge, proxy servers (Waterfall, Velocity, Bungeecord), and more
- **Multiple server types**: Vanilla, Paper and its forks (Folia, Purpur, Leaf, Pufferfish), mod loaders (Forge, NeoForge, Fabric, Quilt), hybrids (Mohist, Banner), SpongeVanilla and proxy servers (Velocity, Waterfall, Bungeecord)
- **Automatic Java management**: Detects, installs, and manages Java versions
- **Official API integration**: Downloads from official sources (Mojang, PaperMC, Fabric, Forge)
- **Official API integration**: Downloads from official sources and verifies the published checksums
- **Smart version mapping**: Automatically selects correct Java version for each Minecraft version
- **Panel friendly**: console input is passed straight to the server, stop signals shut it down gracefully, and an installed server still starts when a download API is unreachable
- **Configuration files**: Global and per-server YAML configuration

## Installation
Expand Down Expand Up @@ -70,7 +71,10 @@ mcrun run --version=1.20.4 --accept-eula
mcrun run --mod=paper --version=1.20.4 --rcon-port=25575 --rcon-password=secret --accept-eula

# Run Forge with custom memory
mcrun run --mod=forge --version=1.20.4 --memory=8G --min-memory=4G --accept-eula
mcrun run --mod=forge --version=1.20.1 --memory=8G --min-memory=4G --accept-eula

# Run a specific NeoForge version
mcrun run --mod=neoforge --version=1.21.1 --mod-version=21.1.251 --accept-eula

# Run Waterfall proxy
mcrun run --mod=waterfall --version=1.21
Expand All @@ -82,6 +86,35 @@ mcrun run --mod=velocity
mcrun run --mod=bungeecord
```

#### Installation and updates

`mcrun run` asks the provider which file serves the requested version, installs it if
that exact version is not in the server directory yet, and starts it:

- Without `--mod-version` the newest build is used, so a restart picks up a new Paper
build or a new recommended Forge. The JAR a newer build replaced is removed.
- Installer-based servers (Forge, NeoForge, Quilt) are installed once per version. Forge
1.17+ and NeoForge are started from the JVM argument file the installer writes
(`@libraries/.../unix_args.txt`), older Forge from its JAR. A `user_jvm_args.txt` in
the server directory is honoured; `--memory` and `--min-memory` win over it.
- What was installed is recorded in `.mcrun-state.json`. When the provider's API cannot
be reached, the installed server is started with a warning instead of failing - as
long as it matches the requested mod and version.

#### Stopping and console input

The server reads the console input of `mcrun` directly, so a control panel can write
commands to the stdin of `mcrun`. When stdin is not a terminal, `-Dterminal.jline=false`
is passed to make Paper, Forge and the like read plain lines; pass
`--jvm-args=-Dterminal.jline=true` to override.

On `SIGINT`, `SIGTERM` and `SIGHUP` the server is asked to stop and gets 90 seconds to
save the world before it is killed; `mcrun` then exits with code 0. If the server stops
on its own with an error, `mcrun` exits with the same exit code.

The selected Java is put first on `PATH` and exported as `JAVA_HOME` for the server
process: hybrid servers restart themselves through `java` after installing their libraries.

### list

List available server versions.
Expand Down Expand Up @@ -177,8 +210,8 @@ mcrun install java --version=21 --set-default
```
-c, --config string Config file path (default: ~/.mcrun/config.yaml)
-d, --dir string Server working directory (default: current directory)
-m, --mod string Server type: vanilla, paper, forge, fabric, waterfall, velocity, bungeecord, spigot, craftbukkit, cauldron
--mod-version string Mod-specific version (e.g., Paper build number)
-m, --mod string Server type, see "Supported Server Types" (default: vanilla)
--mod-version string Mod-specific version (Paper build, Forge or NeoForge version, Fabric/Quilt loader, ...)
--version string Minecraft version (e.g., 1.20.4)
--java int Java version override (8, 11, 17, 21, 25)
--java-path string Custom Java binary path
Expand All @@ -189,25 +222,40 @@ mcrun install java --version=21 --set-default

### Game Servers

| Type | Description | Download Source |
|------|-------------|-----------------|
| `vanilla` | Official Minecraft server | Mojang API |
| `paper` | Paper (high-performance Spigot fork) | PaperMC API |
| `fabric` | Fabric mod loader | Fabric Meta API |
| `forge` | Forge mod loader | Forge Maven |
| `spigot` | Spigot (suggests Paper) | - |
| `craftbukkit` | CraftBukkit (suggests Paper) | - |
| `cauldron` | Cauldron (legacy, MC 1.7.10 max) | Archived |
| Type | Description | Download Source | `--mod-version` |
|-----------------|-----------------------------------------------|--------------------------|-----------------------|
| `vanilla` | Official Minecraft server | Mojang API | - |
| `paper` | Paper (high-performance Spigot fork) | PaperMC Fill API v3 | build number |
| `folia` | Folia (regionised multithreading, by PaperMC) | PaperMC Fill API v3 | build number |
| `purpur` | Purpur (Paper fork) | Purpur API | build number |
| `leaf` | Leaf (Paper fork) | Leaf API | build number |
| `pufferfish` | Pufferfish (Paper fork) | Pufferfish Jenkins | build number |
| `fabric` | Fabric mod loader | Fabric Meta API | loader version |
| `quilt` | Quilt mod loader | Quilt Meta API + Maven | loader version |
| `forge` | Forge mod loader | Forge Maven | Forge version |
| `neoforge` | NeoForge mod loader (1.20.1+) | NeoForged Maven | NeoForge version |
| `mohist` | Mohist (Forge + Bukkit hybrid) | MohistMC API | build number |
| `banner` | Banner (Fabric + Bukkit hybrid) | MohistMC API | build number |
| `spongevanilla` | SpongeVanilla (Sponge API) | Sponge Downloads API | SpongeVanilla version |
| `spigot` | Alias: installs Paper | PaperMC Fill API v3 | build number |
| `craftbukkit` | Alias: installs Paper | PaperMC Fill API v3 | build number |
| `cauldron` | Alias: installs Mohist 1.7.10 | MohistMC API | build number |

Without `--version` the newest release with a stable build is used; a version that only
has pre-release builds yet (for example ALPHA builds of Paper right after a Minecraft
release) can still be requested explicitly.

### Proxy Servers

| Type | Description | Download Source |
|------|-------------|-----------------|
| `waterfall` | Waterfall proxy (BungeeCord fork by PaperMC) | PaperMC API |
| `velocity` | Velocity proxy (modern, high-performance) | PaperMC API |
| `bungeecord` | BungeeCord proxy (original) | Jenkins CI |
| Type | Description | Download Source |
|--------------|----------------------------------------------|---------------------|
| `waterfall` | Waterfall proxy (BungeeCord fork by PaperMC) | PaperMC Fill API v3 |
| `velocity` | Velocity proxy (modern, high-performance) | PaperMC Fill API v3 |
| `bungeecord` | BungeeCord proxy (original) | Jenkins CI |

**Note:** Proxy servers don't require EULA acceptance or server.properties - they use their own `config.yml` configuration.
**Note:** Proxy servers don't require EULA acceptance or server.properties - they use their own configuration
(`velocity.toml`, `config.yml`). `--port` is passed to Velocity on the command line; Waterfall and Bungeecord
take their listen address from `config.yml` only.

## Java Version Requirements

Expand All @@ -225,7 +273,9 @@ metadata is unavailable, this fallback table is used:
| 1.17 - 1.17.1 | Java 17 |
| 1.16.5 and older | Java 8 |

**Proxy servers** (Waterfall, Velocity, Bungeecord) require **Java 17**.
**Proxy servers**: Waterfall and Bungeecord run on **Java 17**, Velocity on **Java 25**.

On Alpine and other musl based systems the Alpine builds of Temurin are installed.

## Configuration

Expand Down Expand Up @@ -262,7 +312,8 @@ cache:

### Per-Server Configuration

Location: `.mcrun.yaml` in server directory
Location: `.mcrun.yaml` in server directory. Its values are the defaults of the
corresponding flags: a flag given on the command line always wins.

```yaml
version: "1.20.4"
Expand All @@ -286,7 +337,7 @@ server:

## Default JVM Arguments

mcrun uses optimized JVM flags by default (Aikar's flags):
Unless `server.jvm_args` is set in the config, mcrun starts the server with:

```
-XX:+UseG1GC
Expand All @@ -295,18 +346,19 @@ mcrun uses optimized JVM flags by default (Aikar's flags):
-XX:+UnlockExperimentalVMOptions
-XX:+DisableExplicitGC
-XX:+AlwaysPreTouch
-XX:G1NewSizePercent=30
-XX:G1MaxNewSizePercent=40
-XX:G1HeapRegionSize=8M
-XX:G1ReservePercent=20
-XX:G1HeapWastePercent=5
-XX:G1MixedGCCountTarget=4
-XX:InitiatingHeapOccupancyPercent=15
-XX:G1MixedGCLiveThresholdPercent=90
-XX:G1RSetUpdatingPauseTimePercent=5
-XX:SurvivorRatio=32
-XX:+PerfDisableSharedMem
-XX:MaxTenuringThreshold=1
```

The heap is `-Xmx2G -Xms1G` by default (`--memory`, `--min-memory`); an initial heap
above the maximum is lowered to it. For large heaps consider the full set of
[Aikar's flags](https://docs.papermc.io/paper/aikars-flags) through `server.jvm_args`.

## Development

```bash
go vet ./... && go test ./...

# Resolve the default server of every provider against the real download APIs
go test -tags live ./internal/providers/ -run TestLiveResolve -v
```

## License
Expand Down
30 changes: 30 additions & 0 deletions cmd/mcrun/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package mcrun

import "github.com/gameap/minecraft-runner/internal/providers"

// createRegistry creates a provider registry with all providers
func createRegistry() *providers.Registry {
registry := providers.NewRegistry()

registry.Register(providers.NewVanillaProvider())
registry.Register(providers.NewPaperProvider())
registry.Register(providers.NewFoliaProvider())
registry.Register(providers.NewPurpurProvider())
registry.Register(providers.NewLeafProvider())
registry.Register(providers.NewPufferfishProvider())
registry.Register(providers.NewFabricProvider())
registry.Register(providers.NewQuiltProvider())
registry.Register(providers.NewForgeProvider())
registry.Register(providers.NewNeoForgeProvider())
registry.Register(providers.NewMohistProvider())
registry.Register(providers.NewBannerProvider())
registry.Register(providers.NewSpongeVanillaProvider())
registry.Register(providers.NewSpigotProvider())
registry.Register(providers.NewCraftBukkitProvider())
registry.Register(providers.NewCauldronProvider())
registry.Register(providers.NewWaterfallProvider())
registry.Register(providers.NewVelocityProvider())
registry.Register(providers.NewBungeecordProvider())

return registry
}
65 changes: 54 additions & 11 deletions cmd/mcrun/root.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package mcrun

import (
"errors"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"

"github.com/gameap/minecraft-runner/internal/config"
"github.com/gameap/minecraft-runner/internal/server"
"github.com/gameap/minecraft-runner/internal/utils"
)

var (
Expand Down Expand Up @@ -54,39 +58,78 @@ var rootCmd = &cobra.Command{
Cross-platform CLI tool to download and run Minecraft servers
with integrated Java management.

Supported server types: vanilla, paper, forge, fabric, spigot, craftbukkit, cauldron,
waterfall, velocity, bungeecord`,
Supported server types:
` + supportedModsHelp(),
// A server that exits with an error is not a usage mistake, and Execute
// reports the error itself
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
utils.SetVersion(Version)

var err error
cfg, err = config.Load(cfgFile, serverDir)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}

// Apply config defaults if flags not set
if mcVersion == "" && cfg.Defaults.Version != "" {
mcVersion = cfg.Defaults.Version
}
if mod == "" && cfg.Defaults.Mod != "" {
mod = cfg.Defaults.Mod
}
applyConfigDefaults(cmd)

return nil
},
}

// applyConfigDefaults lets the config files stand in for flags that were not
// given. The mod flag has a default of its own, so only Changed can tell
// whether the user asked for vanilla.
func applyConfigDefaults(cmd *cobra.Command) {
flags := cmd.Flags()

if !flags.Changed("version") && cfg.Defaults.Version != "" {
mcVersion = cfg.Defaults.Version
}
if !flags.Changed("mod") && cfg.Defaults.Mod != "" {
mod = cfg.Defaults.Mod
}
if !flags.Changed("mod-version") && cfg.Defaults.ModVersion != "" {
modVersion = cfg.Defaults.ModVersion
}
if !flags.Changed("java") && cfg.Java.Version > 0 {
javaVersion = cfg.Java.Version
}
if !flags.Changed("java-path") && cfg.Java.Path != "" {
javaPath = cfg.Java.Path
}
}

func supportedModsHelp() string {
return " " + strings.Join(createRegistry().List(), ", ")
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
err := rootCmd.Execute()
if err == nil {
return
}

var exitErr *server.ExitError
if errors.As(err, &exitErr) {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
if exitErr.Code > 0 {
os.Exit(exitErr.Code)
}
os.Exit(1)
}

fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default: ~/.mcrun/config.yaml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "V", false, "enable verbose output")
rootCmd.PersistentFlags().StringVar(&mcVersion, "version", "", "Minecraft version (e.g., 1.20.4)")
rootCmd.PersistentFlags().StringVarP(&mod, "mod", "m", "vanilla", "server mod type (vanilla, paper, forge, fabric, spigot, craftbukkit, cauldron, waterfall, velocity, bungeecord)")
rootCmd.PersistentFlags().StringVarP(&mod, "mod", "m", "vanilla", "server mod type (see the list of supported server types)")
rootCmd.PersistentFlags().StringVar(&modVersion, "mod-version", "", "mod-specific version")
rootCmd.PersistentFlags().IntVar(&javaVersion, "java", 0, "Java version override (8, 11, 17, 21, 25)")
rootCmd.PersistentFlags().StringVar(&javaPath, "java-path", "", "custom Java binary path")
Expand Down
Loading
Loading