diff --git a/README.MD b/README.MD index b6b8324..c7657f3 100644 --- a/README.MD +++ b/README.MD @@ -19,15 +19,17 @@ This is a minecraft server online installer You can use this cli to easily install minecraft server. We also support some types of servers and modpacks -| Server Type | Support | -|-------------|---------| -| Vanilla | true | -| Fabric | true | -| Forge | true | -| Quilt | true | -| Spigot | true | -| PaperMC | TODO | -| ArcLight | TODO | +| Server Type | Support | +|--------------|---------| +| Vanilla | true | +| Fabric | true | +| Forge | true | +| Quilt | true | +| Spigot | true | +| PaperMC | true | +| ArcLight | true | +| Mohist | TODO | +| Catserver | TODO | | Modpack Type | Support | |--------------|---------| @@ -51,7 +53,7 @@ Flags: the version of the server need to be installed, default is the latest (default "latest") Args: string - type of the server [fabric forge quilt spigot vanilla] (default "vanilla" ) + type of the server [fabric forge quilt spigot vanilla papermc arclight] (default "vanilla" ) filepath | URL the modpack's local path or an URL. If it's an URL, installer will download the modpack first ``` @@ -80,6 +82,12 @@ minecraft_installer -name minecraft_server -version 1.16.5 -server forge minecraft_installer -name minecraft_server -version 1.19.2 -server fabric -path server ``` +```sh +# Install papermc 1.14.4 server into server/minecraft_server.jar +minecraft_installer -name minecraft_server -version 1.14.4 -server papermc +# papermc and arclight installation will automatically generate different directories for different builder. +``` + ### Install modpacks ```sh @@ -105,8 +113,8 @@ minecraft_installer versions minecraft_installer -version snapshot versions ``` + ## TODO -- [ ] PaperMC - [ ] Search modpacks from modrinth - [ ] Configurable proxy diff --git a/README_zh.MD b/README_zh.MD index 3edb2da..e629707 100644 --- a/README_zh.MD +++ b/README_zh.MD @@ -22,8 +22,10 @@ | Forge | 是 | | Quilt | 是 | | Spigot | 是 | -| PaperMC | 进行中 | -| ArcLight | 进行中 | +| PaperMC | 是 | +| ArcLight | 是 | +| Mohist | 计划中/否 | +| Catserver | 计划中/否 | | 整合包类型 | 支持 | |--------------|----------| @@ -76,6 +78,12 @@ minecraft_installer -name minecraft_server -version 1.16.5 -server forge minecraft_installer -name minecraft_server -version 1.19.2 -server fabric -path server ``` +```sh +# 将 papermc 1.14.4 服务端下载到 server/minecraft_server.jar +minecraft_installer -name minecraft_server -version 1.14.4 -server papermc +# 注:papermc以及arclight服务器因build区别会区分到不同文件夹中,执行中会自动建立并命名该server文件夹,更加方便识别 +``` + ### 安装整合包 ```sh @@ -100,3 +108,7 @@ minecraft_installer versions ```sh minecraft_installer -version snapshot versions ``` +## 计划中 + +- [ ] 从modrinth搜寻整合包 +- [ ] 代理相关 diff --git a/arclight_installer.go b/arclight_installer.go new file mode 100644 index 0000000..247e3eb --- /dev/null +++ b/arclight_installer.go @@ -0,0 +1,195 @@ +package installer + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" +) + +type ( + ArclightInstaller struct { + } + + ArclightRelease struct { + Assets []ArclightAssets `json:"assets"` + IsExpired bool + PublishTime string `json:"published_at"` + } + + ArclightAssets struct { + AssetsUrl string `json:"url"` + AssetsName string `json:"name"` + DownloadUrl string `json:"browser_download_url"` + } +) + +var DefaultArclightInstaller = &ArclightInstaller{} + +var _ Installer = DefaultArclightInstaller + +func init() { + Installers["arclight"] = DefaultArclightInstaller +} + +func (r *ArclightInstaller) Install(path, name string, target string) (installed string, err error) { + return r.InstallWithLoader(path, name, target, "") +} + +func (r *ArclightInstaller) InstallWithLoader(path, name string, target string, loader string) (installed string, err error) { + data, err := r.GetInstallerVersions() + if err != nil { + return "", err + } + if len(loader) == 0 { + allVersions := r.GetOnlyVersions(data) + if target == "latest" { + loader, err = r.GetLatestVersion() + if err != nil { + return "", err + } + goto DownloadPart + } + for i := 0; i < len(allVersions); i += 1 { + if allVersions[i] == target { + loader = target + goto DownloadPart + } + } + loger.Info("not find the suitable builder, the version should be included in the following list:") + for i := 0; i < len(allVersions); i += 1 { + if data[allVersions[i]].IsExpired == true { + loger.Info("versions:", allVersions[i], " EXPIRED, DO NOT SUPPORT") + } else { + loger.Info("versions:", allVersions[i]) + } + } + return "", &VersionNotFoundErr{target} + } +DownloadPart: + ExactDownloadeName := data[loader].Assets[0].AssetsName + ArclightInstallerUrl := data[loader].Assets[0].DownloadUrl + if data[loader].IsExpired == true { + loger.Fatal("Sorry, the one you choose has already expired, try another version.") + return "", &VersionNotFoundErr{target} + } + var buildJar string + if buildJar, err = DefaultHTTPClient.DownloadDirect(ArclightInstallerUrl, ExactDownloadeName, downloadingCallback(ArclightInstallerUrl)); err != nil { + return + } + installed, err = r.Runbuilder(buildJar, ExactDownloadeName, path) + if err != nil { + loger.Info("an error occurred while running the server jar file, but you can still do that manually.") + loger.Error(err) + } + return +} + +func (r *ArclightInstaller) ListVersions(snapshot bool) (versions []string, err error) { + data, err := r.GetInstallerVersions() + if err != nil { + return + } + var dataVersions []string = r.GetOnlyVersions(data) + for _, v := range dataVersions { + versions = append(versions, v) + } + return +} + +func (r *ArclightInstaller) GetLatestVersion() (version string, err error) { + data, err := r.GetInstallerVersions() + if err != nil { + return + } + var dataVersions []string = r.GetOnlyVersions(data) + var v0, v1 Version + for _, v := range dataVersions { + if v1, err = VersionFromString(v); err != nil { + return + } + if v0.Less(v1) { + v0 = v1 + } + } + version = v0.String() + return +} + +func (r *ArclightInstaller) GetInstallerVersions() (map[string]ArclightRelease, error) { + data := make(map[string]ArclightRelease) + link := "https://api.github.com/repos/IzzelAliz/Arclight/releases" + var releases []ArclightRelease + err := DefaultHTTPClient.GetJson(link, &releases) + if err != nil { + return data, err + } + for i := 0; i < len(releases); i += 1 { + details := strings.Split(releases[i].Assets[0].AssetsName, "-") + //details should be ["arclight","forge","{VERSION}","{BUILDNUM}.jar"], so append value of index 2 + timeDetails := strings.Split(releases[i].PublishTime, "-") + //time should be "{YEAR}-{MONTH}-{DATE}T{CLOCK}}" + year, err := strconv.Atoi(timeDetails[0]) + if err != nil { + return data, err + } + month, err := strconv.Atoi(timeDetails[1]) + if err != nil { + return data, err + } + if year < 2024 || (year == 2024 && month < 2) { + releases[i].IsExpired = true + } else { + releases[i].IsExpired = false + } + if len(data[details[2]].Assets) == 0 { + data[details[2]] = releases[i] + } + //to get the newest builder for each version + } + return data, err +} + +func (r *ArclightInstaller) GetOnlyVersions(data map[string]ArclightRelease) (versions []string) { + for k := range data { + versions = append(versions, k) + } + return +} + +func (r *ArclightInstaller) Runbuilder(buildJar string, ExactDownloadName string, path string) (installed string, err error) { + currentDir, err := os.Getwd() + if err != nil { + return + } + serverDirectory := filepath.Join(currentDir, "server-"+ExactDownloadName[0:len(ExactDownloadName)-4]) + os.RemoveAll(serverDirectory) + err = os.MkdirAll(serverDirectory, os.ModePerm) + if err != nil { + return + } + err = os.Rename(buildJar, filepath.Join(serverDirectory, ExactDownloadName)) + if err != nil { + return + } + buildJar = filepath.Join(serverDirectory, ExactDownloadName) + loger.Info("Server jar file is successfully installed in path: " + buildJar) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + javapath, err := lookJavaPath() + if err != nil { + return + } + cmd := exec.CommandContext(ctx, javapath, "-jar", buildJar) + cmd.Dir = filepath.Join(path, "server-"+ExactDownloadName[0:len(ExactDownloadName)-4]) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + loger.Infof("Running %q...", cmd.String()) + if err = cmd.Run(); err != nil { + return + } + installed = buildJar + "\n" + return +} diff --git a/changelogs/v1.2.4.MD b/changelogs/v1.2.4.MD new file mode 100644 index 0000000..8725826 --- /dev/null +++ b/changelogs/v1.2.4.MD @@ -0,0 +1,9 @@ + +#### Adds + +- Add the support of Papermc server downloading and available versions inquiry +- Add the support of Arclight server downloading and available versions inquiry + +#### Changes + +- Support new types of server: Papermc, Arclight diff --git a/cli/main.go b/cli/main.go index c5a6c27..f369042 100644 --- a/cli/main.go +++ b/cli/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "io" "net/url" "os" @@ -11,6 +12,36 @@ import ( installer "github.com/kmcsr/server-installer" ) +const UsageText = ` +minecraft_installer [...flags] +minecraft_installer [...flags] modpack +minecraft_installer [...flags] versions [] + +Example: + Install servers: + minecraft_installer -name minecraft_server -version 1.7.10 vanilla + Install minecraft 1.7.10 vanilla server into minecraft_server.jar + minecraft_installer -name minecraft_server -version 1.19.2 forge + Install minecraft 1.19.2 forge server into current directory and the executable is minecraft_server.sh + Hint: forge installer will make run scripts for the minecraft version that higher or equal than 1.17 + for version that less than 1.17, you still need to use 'java -jar' to run the server + minecraft_installer -name minecraft_server -version 1.19.2 -output server fabric + Install minecraft 1.19.2 fabric server into server/minecraft_server.jar + Install modpacks: + minecraft_installer -name modpack_server modpack /path/to/modrinth-modpack.mrpack + Install the modpack from local to the current directory + Hint: Only support modrinth modpack for now, curseforge is in progress + minecraft_installer -name modpack_server modpack 'https://cdn-raw.modrinth.com/data/sl6XzkCP/versions/i4agaPF2/Automation%20v3.3.mrpack' + Install the modpack from internet to the current directory + Hint: if you want to install modpack from the internet, + you must add the prefixs [https://, http://] + List Versions: + minecraft_installer versions + List all vanilla versions but without snapshots + minecraft_installer -version snapshot versions + List all vanilla versions include snapshots +` + var loger logger.Logger func initLogger() { @@ -43,7 +74,7 @@ func parseArgs() { flag.Usage = func() { out := flag.CommandLine.Output() fmt.Fprintf(out, "Usage of %s (%s):\n", os.Args[0], installer.PkgVersion) - fmt.Fprint(out, UsageText) + io.WriteString(out, UsageText) fmt.Fprintln(out, "Flags:") fmt.Fprintln(out, " -h, -help") fmt.Fprintln(out, " Show this help page") @@ -101,6 +132,10 @@ func main() { installed, err = installer.DefaultFabricInstaller.InstallWithLoader(InstallPath, ExecutableName, minecraft, fabric) } else if quilt, ok := pack.Deps["quilt-loader"]; ok { installed, err = installer.DefaultQuiltInstaller.InstallWithLoader(InstallPath, ExecutableName, minecraft, quilt) + } else if papermc, ok := pack.Deps["papermc-loader"]; ok { + installed, err = installer.DefaultQuiltInstaller.InstallWithLoader(InstallPath, ExecutableName, minecraft, papermc) + } else if arclight, ok := pack.Deps["arclight-loader"]; ok { + installed, err = installer.DefaultQuiltInstaller.InstallWithLoader(InstallPath, ExecutableName, minecraft, arclight) } else if mok { installed, err = installer.VanillaIns.Install(InstallPath, ExecutableName, minecraft) } else { diff --git a/cli/usage.go b/cli/usage.go index 49cddd0..06ab7d0 100644 --- a/cli/usage.go +++ b/cli/usage.go @@ -1,31 +1 @@ package main - -const UsageText = ` -minecraft_installer [...flags] -minecraft_installer [...flags] modpack -minecraft_installer [...flags] versions [] - -Example: - Install servers: - minecraft_installer -name minecraft_server -version 1.7.10 vanilla - Install minecraft 1.7.10 vanilla server into minecraft_server.jar - minecraft_installer -name minecraft_server -version 1.19.2 forge - Install minecraft 1.19.2 forge server into current directory and the executable is minecraft_server.sh - Hint: forge installer will make run scripts for the minecraft version that higher or equal than 1.17 - for version that less than 1.17, you still need to use 'java -jar' to run the server - minecraft_installer -name minecraft_server -version 1.19.2 -output server fabric - Install minecraft 1.19.2 fabric server into server/minecraft_server.jar - Install modpacks: - minecraft_installer -name modpack_server modpack /path/to/modrinth-modpack.mrpack - Install the modpack from local to the current directory - Hint: Only support modrinth modpack for now, curseforge is in progress - minecraft_installer -name modpack_server modpack 'https://cdn-raw.modrinth.com/data/sl6XzkCP/versions/i4agaPF2/Automation%20v3.3.mrpack' - Install the modpack from internet to the current directory - Hint: if you want to install modpack from the internet, - you must add the prefixs [https://, http://] - List Versions: - minecraft_installer versions - List all vanilla versions but without snapshots - minecraft_installer -version snapshot versions - List all vanilla versions include snapshots -` diff --git a/httpclient.go b/httpclient.go index f2cc084..9e28ed2 100644 --- a/httpclient.go +++ b/httpclient.go @@ -193,3 +193,40 @@ func (c *HTTPClient) PostForm(url string, form url.Values) (res *http.Response, return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(formStr)) } + +func (c *HTTPClient) DownloadDirect(url string, ExactDownloadeName string, cb DlCallback) (installed string, err error) { + resp, err := http.Head(url) + if err != nil { + return + } + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return + } + resp, err = http.DefaultClient.Do(req) + if err != nil { + return + } + defer resp.Body.Close() + filename := filepath.Base(url) + flags := os.O_CREATE | os.O_WRONLY + f, err := os.OpenFile(filename, flags, 0666) + if err != nil { + return + } + defer f.Close() + + buf := make([]byte, 16*1024) + _, err = io.CopyBuffer(f, resp.Body, buf) + if err != nil { + if err == io.EOF { + return + } + } + cpath, err := os.Getwd() + if err != nil { + return + } + installed = filepath.Join(cpath, ExactDownloadeName) + return +} diff --git a/papermc_installer.go b/papermc_installer.go new file mode 100644 index 0000000..9da5367 --- /dev/null +++ b/papermc_installer.go @@ -0,0 +1,157 @@ +package installer + +import ( + "context" + "net/url" + "os" + "os/exec" + "path/filepath" + "strconv" +) + +type ( + PapermcInstaller struct { + PaperUrl string + } + + PapermcVersions struct { + Pid string `json:"project_id"` + Pname string `json:"project_name"` + VersionsGroup []string `json:"version_groups"` + PaperVersions []string `json:"versions"` + } + + PapermcBuilders struct { + Pid string `json:"project_id"` + Pname string `json:"project_name"` + TgVersion string `json:"version"` + Builders []int `json:"builds"` + } +) + +var DefaultPapermcInstaller = &PapermcInstaller{ + PaperUrl: "https://api.papermc.io/v2/projects/paper/versions", +} +var _ Installer = DefaultPapermcInstaller + +func init() { + Installers["papermc"] = DefaultPapermcInstaller +} + +func (r *PapermcInstaller) Install(path, name string, target string) (installed string, err error) { + return r.InstallWithLoader(path, name, target, "") +} + +func (r *PapermcInstaller) InstallWithLoader(path, name string, target string, loader string) (installed string, err error) { + if len(loader) == 0 { + allVersions, err := r.GetInstallerVersions() + if err != nil { + return "", err + } + if target == "latest" { + loader = allVersions[len(allVersions)-1] + goto DownloadPart + } + for i := 0; i < len(allVersions); i += 1 { + if allVersions[i] == target { + loader = target + goto DownloadPart + } + } + loger.Info("not find the suitable builder, the version should be included in the following list:") + for i := 0; i < len(allVersions); i += 1 { + loger.Info("versions:", allVersions[i]) + } + return "", &VersionNotFoundErr{target} + } +DownloadPart: + buildNumInt, err := r.GetBuildNumber(loader) + if err != nil { + return + } + buildNum := strconv.Itoa(buildNumInt) + ExactDownloadeName := "paper-" + loader + "-" + buildNum + ".jar" + PapermcInstallerUrl, err := url.JoinPath(r.PaperUrl, loader, "builds", buildNum, "downloads/"+ExactDownloadeName) + if err != nil { + return + } + loger.Infof("Getting papermc server installer %s at %q...", ExactDownloadeName, PapermcInstallerUrl) + var buildJar string + if buildJar, err = DefaultHTTPClient.DownloadDirect(PapermcInstallerUrl, ExactDownloadeName, downloadingCallback(PapermcInstallerUrl)); err != nil { + return + } + installed, err = r.Runbuilder(buildJar, ExactDownloadeName, path) + if err != nil { + loger.Info("an error occurred while running the server jar file, but you can still do that manually.") + loger.Error(err) + } + return +} + +func (r *PapermcInstaller) ListVersions(snapshot bool) (versions []string, err error) { + data, err := r.GetInstallerVersions() + if err != nil { + return + } + for _, v := range data { + versions = append(versions, v) + } + return +} + +func (r *PapermcInstaller) GetInstallerVersions() (data []string, err error) { + link := "https://api.papermc.io/v2/projects/paper" + var versions PapermcVersions + err = DefaultHTTPClient.GetJson(link, &versions) + if err != nil { + return + } + data = versions.PaperVersions + return data, err +} + +func (r *PapermcInstaller) GetBuildNumber(version string) (buildNum int, err error) { + buildUrl := "https://api.papermc.io/v2/projects/paper/versions/" + version + var builders PapermcBuilders + err = DefaultHTTPClient.GetJson(buildUrl, &builders) + if err != nil { + return + } + buildNum = builders.Builders[len(builders.Builders)-1] + return buildNum, err +} + +func (r *PapermcInstaller) Runbuilder(buildJar string, ExactDownloadName string, path string) (installed string, err error) { + currentDir, err := os.Getwd() + if err != nil { + return + } + serverDirectory := filepath.Join(currentDir, "server-"+ExactDownloadName[0:len(ExactDownloadName)-4]) + os.RemoveAll(serverDirectory) + err = os.MkdirAll(serverDirectory, os.ModePerm) + if err != nil { + return + } + err = os.Rename(buildJar, filepath.Join(serverDirectory, ExactDownloadName)) + if err != nil { + return + } + buildJar = filepath.Join(serverDirectory, ExactDownloadName) + loger.Info("Server jar file is successfully installed in path: " + buildJar) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + javapath, err := lookJavaPath() + if err != nil { + return + } + cmd := exec.CommandContext(ctx, javapath, "-jar", buildJar) + cmd.Dir = filepath.Join(path, "server-"+ExactDownloadName[0:len(ExactDownloadName)-4]) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + loger.Infof("Running %q...", cmd.String()) + if err = cmd.Run(); err != nil { + return + } + installed = buildJar + "\n" + return +}