diff --git a/README.MD b/README.MD index b6b8324..b293237 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 and rename 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..f105dbb 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,8 @@ 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..c879512 --- /dev/null +++ b/arclight_installer.go @@ -0,0 +1,189 @@ +package installer + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" +) + +type ( + ArclightInstaller struct {} + + ArclightAssets struct { + AssetsUrl string `json:"url"` + AssetsName string `json:"name"` + DownloadUrl string `json:"browser_download_url"` + } + + ArclightRelease struct { + Assets []ArclightAssets `json:"assets"` + IsExpired bool + PublishTime string `json:"published_at"` + } +) + +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) { + versions, err := r.GetInstallerVersions() + if err != nil { + return "", err + } + if len(loader) == 0 { + var alreadyFind bool = false + allVersions := r.GetOnlyVersions(versions) + if target == "latest" { + loader, err = r.GetLatestVersion() + if err != nil { + return "", err + } + alreadyFind = true + } + for _, version := range allVersions { + if version == target { + loader = target + alreadyFind = true + } + } + if !alreadyFind { + loger.Info("not find the suitable builder, the version should be included in the following list:") + for _, version := range allVersions { + if versions[version].IsExpired { + loger.Info("versions:", version, " EXPIRED, DO NOT SUPPORT") + } else { + loger.Info("versions:", version) + } + } + return "", &VersionNotFoundErr{target} + } + } + exactDownloadeName := versions[loader].Assets[0].AssetsName + ArclightInstallerUrl := versions[loader].Assets[0].DownloadUrl + if version, ok := versions[loader]; ok && version.IsExpired { + 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) ([]string, error) { + additionalVersions, err := r.GetInstallerVersions() + if err != nil { + return nil, err + } + return r.GetOnlyVersions(additionalVersions), err +} + +func (r *ArclightInstaller) GetLatestVersion() (version string, err error) { + additionalVersions, err := r.GetInstallerVersions() + if err != nil { + return + } + var dataVersions []string = r.GetOnlyVersions(additionalVersions) + 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) { + additionalVersions := 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 additionalVersions, err + } + for _, release := range releases { + details := strings.Split(release.Assets[0].AssetsName, "-") + // details should be ["arclight","forge","{VERSION}","{BUILDNUM}.jar"], so append value of index 2 + layout := "2006-01-02T14:41:48Z" + timeDetails, err := time.Parse(layout, release.PublishTime) + if err != nil{ + return additionalVersions, err + } + year := timeDetails.Year() + month := timeDetails.Month() + expiredYear := 2024 + expiredMonth := time.February + if year < expiredYear || (year == expiredYear && month < expiredMonth) { + release.IsExpired = true + } else { + release.IsExpired = false + } + if len(additionalVersions[details[2]].Assets) == 0 { + additionalVersions[details[2]] = *release + } + // to get the newest builder for each version + } + return additionalVersions, err +} + +func (r *ArclightInstaller) GetOnlyVersions(additionalVersions map[string]ArclightRelease) (versions []string) { + for k, _ := range additionalVersions { + versions = append(versions, k) + } + return +} + +func (r *ArclightInstaller) Runbuilder(buildJar string, ExactDownloadName string, path string) (installed string, err error) { + const SUFFIX_LENGTH int = 4 + NameWithoutSuffix := ExactDownloadName[0 : len(ExactDownloadName)-SUFFIX_LENGTH] + serverDirectory := filepath.Join(".", "server-"+NameWithoutSuffix) + 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)-SUFFIX_LENGTH]) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + loger.Infof("Running %q...", cmd.String()) + if err = cmd.Run(); err != nil { + return + } + installed = buildJar + 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..a4b045a 100644 --- a/cli/main.go +++ b/cli/main.go @@ -101,6 +101,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/httpclient.go b/httpclient.go index f2cc084..0dffa0c 100644 --- a/httpclient.go +++ b/httpclient.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "strings" + "syscall" "time" ) @@ -193,3 +194,37 @@ 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) + f, err := os.OpenFile(filename, syscall.O_CREAT, 0644) + if err != nil { + return + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + 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..3bea5d8 --- /dev/null +++ b/papermc_installer.go @@ -0,0 +1,159 @@ +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", +} +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 { + var alreadyFind bool = false + allVersions, err := r.GetInstallerVersions() + if err != nil { + return "", err + } + if target == "latest" { + loader = allVersions[len(allVersions)-1] + alreadyFind = true + } + for i := 0; i < len(allVersions); i += 1 { + if allVersions[i] == target { + loader = target + alreadyFind = true + } + } + if !alreadyFind { + 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} + } + } + buildNumInt, err := r.GetBuildNumber(loader) + if err != nil { + return + } + buildNum := strconv.Itoa(buildNumInt) + ExactDownloadeName := "paper-" + loader + "-" + buildNum + ".jar" + PapermcInstallerUrl, err := url.JoinPath(r.PaperUrl, "versions", 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, name, 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 := r.PaperUrl + 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, err := url.JoinPath(r.PaperUrl, "versions", version) + if err != nil { + return + } + 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, name string, ExactDownloadName string, path string) (installed string, err error) { + const SUFFIX string = ".jar" + serverDirectory := filepath.Join(".", "server-"+ExactDownloadName[0:len(ExactDownloadName)-len(SUFFIX)]) + os.RemoveAll(serverDirectory) + err = os.MkdirAll(serverDirectory, os.ModePerm) + if err != nil { + return + } + err = os.Rename(buildJar, filepath.Join(serverDirectory, name+SUFFIX)) + if err != nil { + return + } + buildJar = filepath.Join(serverDirectory, name+SUFFIX) + 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)-len(SUFFIX)]) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + loger.Infof("Running %q...", cmd.String()) + if err = cmd.Run(); err != nil { + return + } + installed = buildJar + return +}