parent
889d61b2b1
commit
09a3700b1c
@ -1,3 +1 @@ |
||||
kosmos-reborn-builder |
||||
tmp |
||||
*.zip |
||||
/build |
||||
|
@ -0,0 +1,19 @@ |
||||
TARGET := builder
|
||||
SOURCE := ./cmd/builder/
|
||||
OUTPUT := ./build/
|
||||
|
||||
all: clean deps $(TARGET) |
||||
|
||||
clean: |
||||
@rm -rf ./$(OUTPUT)
|
||||
@echo Cleaned previous build...
|
||||
|
||||
deps: |
||||
@go mod download
|
||||
@echo Dependencies downloaded...
|
||||
|
||||
$(TARGET): |
||||
@mkdir -p ./$(OUTPUT)/modules
|
||||
@cp -R ./assets/* ./$(OUTPUT)/
|
||||
@go build -o ./$(OUTPUT)/$@ $(SOURCE)
|
||||
@echo Kosmos Reborn Builder has been built - $(OUTPUT)$@
|
@ -0,0 +1,96 @@ |
||||
// Kosmos Reborn Builder
|
||||
// Copyright (C) 2021 Nichole Mattera
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
// 02110-1301, USA.
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"flag" |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
"github.com/TeamLibra/Kosmos-Reborn/internal" |
||||
) |
||||
|
||||
type Config struct { |
||||
GithubUsername string |
||||
GithubPassword string |
||||
} |
||||
|
||||
func main() { |
||||
// Parse command line arguments.
|
||||
var version string |
||||
var output string |
||||
|
||||
flag.StringVar(&version, "v", "", "The Kosmos Reborn version. (Required)") |
||||
flag.StringVar(&output, "o", "", "Path of where to generate the zip file. (Required)") |
||||
|
||||
flag.Parse() |
||||
|
||||
if version == "" || output == "" { |
||||
fmt.Println("Usage:") |
||||
flag.PrintDefaults() |
||||
return |
||||
} |
||||
|
||||
// Get config.
|
||||
config := GetConfig() |
||||
if config.GithubUsername == "" || config.GithubPassword == "" { |
||||
fmt.Println("Error: Make sure you have the following environment variables set:") |
||||
fmt.Printf("\tGH_USERNAME - Github Username\n") |
||||
fmt.Printf("\tGH_PASSWORD - Github Password\n") |
||||
return |
||||
} |
||||
|
||||
// Create temp directory.
|
||||
cwd, _ := os.Getwd() |
||||
if internal.Exists(filepath.Join(cwd, "tmp")) { |
||||
os.RemoveAll(filepath.Join(cwd, "tmp")) |
||||
} |
||||
|
||||
tempDirectory := internal.GenerateTempPath() |
||||
os.MkdirAll(tempDirectory, os.ModePerm) |
||||
|
||||
// Start build process.
|
||||
fmt.Printf("Kosmos Reborn %s built with:\n", version) |
||||
buildMessage, err := internal.BuildModules(tempDirectory, version, config.GithubUsername, config.GithubPassword) |
||||
if err == nil { |
||||
// Clean up any old output file.
|
||||
os.RemoveAll(output) |
||||
|
||||
// Create zip file.
|
||||
err = internal.Compress(tempDirectory, filepath.Join(cwd, output)) |
||||
if err != nil { |
||||
fmt.Println("Failed: " + err.Error())
|
||||
} else { |
||||
fmt.Println(buildMessage) |
||||
} |
||||
} else { |
||||
fmt.Println("Failed: " + err.Error()) |
||||
} |
||||
|
||||
// Clean up temp directory.
|
||||
os.RemoveAll(filepath.Join(cwd, "tmp"))
|
||||
} |
||||
|
||||
func GetConfig() Config { |
||||
return Config { |
||||
os.Getenv("GH_USERNAME"), |
||||
os.Getenv("GH_PASSWORD"), |
||||
} |
||||
} |
@ -1,5 +1,5 @@ |
||||
module builder |
||||
module github.com/TeamLibra/Kosmos-Reborn |
||||
|
||||
go 1.15 |
||||
go 1.16 |
||||
|
||||
require github.com/google/uuid v1.2.0 |
||||
require github.com/google/uuid v1.2.0 // indirect |
||||
|
@ -0,0 +1,160 @@ |
||||
// Kosmos Reborn Builder
|
||||
// Copyright (C) 2021 Nichole Mattera
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
// 02110-1301, USA.
|
||||
|
||||
package internal |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"bufio" |
||||
"io" |
||||
"io/ioutil" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
|
||||
"github.com/google/uuid" |
||||
) |
||||
|
||||
func GenerateTempPath() string { |
||||
cwd, _ := os.Getwd() |
||||
return filepath.Join(cwd, "tmp", uuid.New().String()) |
||||
} |
||||
|
||||
func Compress(src, dst string) error { |
||||
zipFile, err := os.Create(dst) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer zipFile.Close() |
||||
|
||||
zipWriter := zip.NewWriter(zipFile) |
||||
defer zipWriter.Close() |
||||
|
||||
err = filepath.Walk(src, func(path string, info os.FileInfo, err error) error { |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if info.IsDir() { |
||||
return nil |
||||
} |
||||
|
||||
file, err := os.Open(path) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer file.Close() |
||||
|
||||
f, err := zipWriter.Create(path[len(src) + 1:]) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
_, err = io.Copy(f, file) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
}) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func CopyFile(src, dst string) error { |
||||
srcfd, err := os.Open(src); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer srcfd.Close() |
||||
|
||||
dstfd, err := os.Create(dst); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer dstfd.Close() |
||||
|
||||
_, err = io.Copy(dstfd, srcfd); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
srcinfo, err := os.Stat(src); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return os.Chmod(dst, srcinfo.Mode()) |
||||
} |
||||
|
||||
func CopyDirectory(src, dst string) error { |
||||
srcinfo, err := os.Stat(src); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
err = os.MkdirAll(dst, srcinfo.Mode()); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
fds, err := ioutil.ReadDir(src); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, fd := range fds { |
||||
srcfp := path.Join(src, fd.Name()) |
||||
dstfp := path.Join(dst, fd.Name()) |
||||
|
||||
if fd.IsDir() { |
||||
err = CopyDirectory(srcfp, dstfp); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
err = CopyFile(srcfp, dstfp); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func Exists(dst string) bool { |
||||
_, err := os.Stat(dst) |
||||
return !os.IsNotExist(err) |
||||
} |
||||
|
||||
func WriteToFile(dst, value string) error { |
||||
file, err := os.Create(dst) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer file.Close() |
||||
|
||||
writer := bufio.NewWriter(file) |
||||
writer.WriteString(value) |
||||
writer.Flush() |
||||
|
||||
return nil |
||||
} |
@ -0,0 +1,159 @@ |
||||
// Kosmos Reborn Builder
|
||||
// Copyright (C) 2021 Nichole Mattera
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
// 02110-1301, USA.
|
||||
|
||||
package internal |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"io" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"path/filepath" |
||||
"regexp" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
type GitHubUser struct { |
||||
Login string `json:"login"` |
||||
Id int `json:"id"` |
||||
NodeId string `json:"node_id"` |
||||
AvatarUrl string `json:"avatar_url"` |
||||
GravatarId string `json:"gravatar_id"` |
||||
Url string `json:"url"` |
||||
HtmlUrl string `json:"html_url"` |
||||
FollowersUrl string `json:"followers_url"` |
||||
FollowingUrl string `json:"following_url"` |
||||
GistsUrl string `json:"gists_url"` |
||||
StarredUrl string `json:"starred_url"` |
||||
SubscriptionsUrl string `json:"subscriptions_url"` |
||||
OrganizationsUrl string `json:"organizations_url"` |
||||
ReposUrl string `json:"repos_url"` |
||||
EventsUrl string `json:"events_url"` |
||||
ReceivedEventsUrl string `json:"received_events_url"` |
||||
Type string `json:"type"` |
||||
SiteAdmin bool `json:"site_admin"` |
||||
} |
||||
|
||||
type GitHubAsset struct { |
||||
Url string `json:"url"` |
||||
Id int `json:"id"` |
||||
NodeId string `json:"node_id"` |
||||
Name string `json:"name"` |
||||
Label string `json:"label"` |
||||
Uploader GitHubUser `json:"uploader"` |
||||
ContentType string `json:"content_type"` |
||||
State string `json:"state"` |
||||
Size int `json:"size"` |
||||
DownloadCount int `json:"download_count"` |
||||
CreatedAt string `json:"created_at"` |
||||
UpdatedAt string `json:"updated_at"` |
||||
BrowserDownloadUrl string `json:"browser_download_url"` |
||||
} |
||||
|
||||
type GitHubRelease struct { |
||||
Url string `json:"url"` |
||||
AssetsUrl string `json:"assets_url"` |
||||
UploadUrl string `json:"upload_url"` |
||||
HtmlUrl string `json:"html_url"` |
||||
Id int `json:"id"` |
||||
Author GitHubUser `json:"author"` |
||||
NodeId string `json:"node_id"` |
||||
TagName string `json:"tag_name"` |
||||
TargetCommitish string `json:"target_commitish"` |
||||
Name string `json:"name"` |
||||
Draft bool `json:"draft"` |
||||
Prerelease bool `json:"prerelease"` |
||||
CreatedAt string `json:"created_at"` |
||||
PublishedAt string `json:"published_at"` |
||||
Assets []GitHubAsset `json:"assets"` |
||||
TarballUrl string `json:"tarball_url"` |
||||
ZipballUrl string `json:"zipball_url"` |
||||
Body string `json:"body"` |
||||
} |
||||
|
||||
func GetLatestRelease(organization string, repository string, assetPattern string, githubUsername string, githubPassword string) (string, string, error) { |
||||
resp, err := http.Get("https://" + githubUsername + ":" + githubPassword + "@api.github.com/repos/" + organization + "/" + repository + "/releases/latest") |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 { |
||||
return "", "", errors.New("Getting latest release returned status code: " + strconv.Itoa(resp.StatusCode)) |
||||
} |
||||
|
||||
defer resp.Body.Close() |
||||
|
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
|
||||
var release GitHubRelease |
||||
json.Unmarshal(body, &release) |
||||
|
||||
for _, asset := range release.Assets { |
||||
matched, err := regexp.Match(assetPattern, []byte(asset.Name)) |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
|
||||
if matched { |
||||
return release.TagName, asset.BrowserDownloadUrl, nil |
||||
} |
||||
} |
||||
|
||||
return "", "", errors.New("No assets") |
||||
} |
||||
|
||||
func DownloadFile(rawUrl string, destination string) (string, error) { |
||||
url, err := url.Parse(rawUrl) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
segments := strings.Split(url.Path, "/") |
||||
fileName := segments[len(segments)-1] |
||||
|
||||
path := filepath.Join(destination, fileName) |
||||
file, err := os.Create(path) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
defer file.Close() |
||||
|
||||
resp, err := http.Get(rawUrl) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 { |
||||
return "", errors.New("Download file returned status code: " + strconv.Itoa(resp.StatusCode)) |
||||
} |
||||
|
||||
defer resp.Body.Close() |
||||
|
||||
_, err = io.Copy(file, resp.Body) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
return path, nil |
||||
} |
@ -0,0 +1,144 @@ |
||||
// Kosmos Reborn Builder
|
||||
// Copyright (C) 2021 Nichole Mattera
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
// 02110-1301, USA.
|
||||
|
||||
package internal |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"errors" |
||||
"io" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
) |
||||
|
||||
type Action string |
||||
const ( |
||||
Copy Action = "copy" |
||||
Delete Action = "delete" |
||||
Extract Action = "extract" |
||||
Mkdir Action = "mkdir" |
||||
) |
||||
|
||||
type Instruction struct { |
||||
Action Action |
||||
Source string |
||||
Destination string |
||||
} |
||||
|
||||
func CopyInstruction(module Module, instruction Instruction, moduleTempDirectory string, tempDirectory string) error { |
||||
matches, err := filepath.Glob(filepath.Join(moduleTempDirectory, instruction.Source)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, match := range matches { |
||||
matchInfo, err := os.Stat(match) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if matchInfo.IsDir() { |
||||
CopyDirectory(match, filepath.Join(tempDirectory, instruction.Destination)) |
||||
} else { |
||||
CopyFile(match, filepath.Join(tempDirectory, instruction.Destination)) |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func DeleteInstruction(module Module, instruction Instruction, moduleTempDirectory string, tempDirectory string) error { |
||||
matches, err := filepath.Glob(filepath.Join(moduleTempDirectory, instruction.Source)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, match := range matches { |
||||
if Exists(match) { |
||||
err = os.Remove(match) |
||||
if err != nil { |
||||
return err |
||||
}
|
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func ExtractInstruction(module Module, instruction Instruction, moduleTempDirectory string, tempDirectory string) error { |
||||
matches, err := filepath.Glob(filepath.Join(moduleTempDirectory, instruction.Source)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(matches) < 1 { |
||||
return errors.New("Nothing to unzip for pattern: " + instruction.Source) |
||||
} |
||||
|
||||
for _, match := range matches { |
||||
zipReader, err := zip.OpenReader(match) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer zipReader.Close() |
||||
|
||||
for _, file := range zipReader.File { |
||||
path := filepath.Join(moduleTempDirectory, file.Name) |
||||
if !strings.HasPrefix(path, filepath.Clean(moduleTempDirectory) + string(os.PathSeparator)) { |
||||
return errors.New("Illegal file path: " + path) |
||||
} |
||||
|
||||
// Extract folder
|
||||
if file.FileInfo().IsDir() { |
||||
os.MkdirAll(path, os.ModePerm) |
||||
continue |
||||
} |
||||
|
||||
// Extract file
|
||||
err = os.MkdirAll(filepath.Dir(path), os.ModePerm); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
defer outFile.Close() |
||||
|
||||
inFile, err := file.Open() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
defer inFile.Close() |
||||
|
||||
_, err = io.Copy(outFile, inFile) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func MkdirInstruction(module Module, instruction Instruction, moduleTempDirectory string, tempDirectory string) error { |
||||
return os.MkdirAll(filepath.Join(tempDirectory, instruction.Destination), os.ModePerm) |
||||
} |
@ -0,0 +1,97 @@ |
||||
// Kosmos Reborn Builder
|
||||
// Copyright (C) 2021 Nichole Mattera
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
// 02110-1301, USA.
|
||||
|
||||
package internal |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"io/ioutil" |
||||
"os" |
||||
) |
||||
|
||||
type Module struct { |
||||
Name string |
||||
Org string |
||||
Repo string |
||||
AssetPattern string |
||||
Instructions []Instruction |
||||
} |
||||
|
||||
func BuildModules(tempDirectory string, version string, githubUsername string, githubPassword string) (string, error) { |
||||
modules, err := ioutil.ReadDir("./modules") |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
buildMessage := "" |
||||
|
||||
for _, file := range modules { |
||||
moduleFile, err := os.Open("./modules/" + file.Name()) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
defer moduleFile.Close() |
||||
|
||||
byteValue, _ := ioutil.ReadAll(moduleFile) |
||||
|
||||
var module Module |
||||
json.Unmarshal(byteValue, &module) |
||||
|
||||
moduleTempDirectory := GenerateTempPath() |
||||
os.MkdirAll(moduleTempDirectory, os.ModePerm) |
||||
|
||||
version, downloadURL, err := GetLatestRelease(module.Org, module.Repo, module.AssetPattern, githubUsername, githubPassword) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
_, err = DownloadFile(downloadURL, moduleTempDirectory) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
for _, instruction := range module.Instructions { |
||||
switch (instruction.Action) { |
||||
case Copy: |
||||
err = CopyInstruction(module, instruction, moduleTempDirectory, tempDirectory) |
||||
break |
||||
|
||||
case Delete: |
||||
err = DeleteInstruction(module, instruction, moduleTempDirectory, tempDirectory) |
||||
break |
||||
|
||||
case Extract: |
||||
err = ExtractInstruction(module, instruction, moduleTempDirectory, tempDirectory) |
||||
break |
||||
|
||||
case Mkdir: |
||||
err = MkdirInstruction(module, instruction, moduleTempDirectory, tempDirectory) |
||||
break |
||||
} |
||||
|
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
} |
||||
|
||||
os.RemoveAll(moduleTempDirectory) |
||||
buildMessage += "\t" + module.Name + ": " + version + "\n" |
||||
} |
||||
|
||||
return buildMessage, nil |
||||
} |
@ -1,16 +0,0 @@ |
||||
TARGET := kosmos-reborn-builder
|
||||
SOURCE := ./src/
|
||||
|
||||
all: clean deps $(TARGET) |
||||
|
||||
clean: |
||||
@rm ./$(TARGET)
|
||||
@echo Cleaned previous build...
|
||||
|
||||
deps: |
||||
@go mod download
|
||||
@echo Dependencies downloaded...
|
||||
|
||||
$(TARGET): |
||||
@go build -o $@ $(SOURCE)
|
||||
@echo Kosmos Reborn Builder has been built - $@
|
@ -1,122 +0,0 @@ |
||||
/* |
||||
* Kosmos Reborn Builder |
||||
* Copyright (C) 2021 Nichole Mattera |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
||||
* 02110-1301, USA. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"flag" |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
) |
||||
|
||||
type Config struct { |
||||
GithubUsername string |
||||
GithubPassword string |
||||
} |
||||
|
||||
type KosmosRebornConfig struct { |
||||
Version string `json:"version"` |
||||
InstalledPackages []string `json:"installed_packages"` |
||||
} |
||||
|
||||
func main() { |
||||
var version string |
||||
var output string |
||||
|
||||
flag.StringVar(&version, "v", "", "The Kosmos Reborn version. (Required)") |
||||
flag.StringVar(&output, "o", "", "Path of where to generate the zip file. (Required)") |
||||
|
||||
flag.Parse() |
||||
|
||||
if version == "" || output == "" { |
||||
fmt.Println("Usage:") |
||||
flag.PrintDefaults() |
||||
return |
||||
} |
||||
|
||||
config := GetConfig() |
||||
if config.GithubUsername == "" || config.GithubPassword == "" { |
||||
fmt.Println("Error: Make sure you have the following environment variables set:") |
||||
fmt.Printf("\tGH_USERNAME - Github Username\n") |
||||
fmt.Printf("\tGH_PASSWORD - Github Password\n") |
||||
return |
||||
} |
||||
|
||||
cwd, _ := os.Getwd() |
||||
|
||||
if Exists(filepath.Join(cwd, "tmp")) { |
||||
os.RemoveAll(filepath.Join(cwd, "tmp")) |
||||
} |
||||
|
||||
tempDirectory := GenerateTempPath() |
||||
os.MkdirAll(tempDirectory, os.ModePerm) |
||||
|
||||
fmt.Printf("Kosmos Reborn %s built with:\n", version) |
||||
|
||||
buildMessage, err := BuildModules(tempDirectory, version, config) |
||||
|
||||
os.RemoveAll(output) |
||||
|
||||
if err == nil { |
||||
err = WriteKosmosRebornConfig(tempDirectory, version) |
||||
if err != nil { |
||||
fmt.Println("Failed: " + err.Error())
|
||||
os.RemoveAll(filepath.Join(cwd, "tmp"))
|
||||
return |
||||
} |
||||
|
||||
err = Compress(tempDirectory, filepath.Join(cwd, output)) |
||||
if err != nil { |
||||
fmt.Println("Failed: " + err.Error())
|
||||
os.RemoveAll(filepath.Join(cwd, "tmp"))
|
||||
return |
||||
} |
||||
|
||||
fmt.Println(buildMessage) |
||||
} else { |
||||
fmt.Println("Failed: " + err.Error()) |
||||
} |
||||
|
||||
os.RemoveAll(filepath.Join(cwd, "tmp"))
|
||||
} |
||||
|
||||
func GetConfig() Config { |
||||
return Config { |
||||
os.Getenv("GH_USERNAME"), |
||||
os.Getenv("GH_PASSWORD"), |
||||
} |
||||
} |
||||
|
||||
func WriteKosmosRebornConfig(tempDirectory string, version string) error { |
||||
config := KosmosRebornConfig{version, []string{}} |
||||
res, err := json.Marshal(config) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
err = WriteToFile(filepath.Join(tempDirectory, "atmosphere", "kosmos-reborn.json"), string(res[:])) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
}
|
@ -1,162 +0,0 @@ |
||||
/* |
||||
* Kosmos Reborn Builder |
||||
* Copyright (C) 2021 Nichole Mattera |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
||||
* 02110-1301, USA. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"bufio" |
||||
"io" |
||||
"io/ioutil" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
|
||||
"github.com/google/uuid" |
||||
) |
||||
|
||||
func GenerateTempPath() string { |
||||
cwd, _ := os.Getwd() |
||||
return filepath.Join(cwd, "tmp", uuid.New().String()) |
||||
} |
||||
|
||||
func Compress(src, dst string) error { |
||||
zipFile, err := os.Create(dst) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer zipFile.Close() |
||||
|
||||
zipWriter := zip.NewWriter(zipFile) |
||||
defer zipWriter.Close() |
||||
|
||||
err = filepath.Walk(src, func(path string, info os.FileInfo, err error) error { |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if info.IsDir() { |
||||
return nil |
||||
} |
||||
|
||||
file, err := os.Open(path) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer file.Close() |
||||
|
||||
f, err := zipWriter.Create(path[len(src) + 1:]) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
_, err = io.Copy(f, file) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return nil |
||||
}) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func CopyFile(src, dst string) error { |
||||
srcfd, err := os.Open(src); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer srcfd.Close() |
||||
|
||||
dstfd, err := os.Create(dst); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer dstfd.Close() |
||||
|
||||
_, err = io.Copy(dstfd, srcfd); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
srcinfo, err := os.Stat(src); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return os.Chmod(dst, srcinfo.Mode()) |
||||
} |
||||
|
||||
func CopyDirectory(src, dst string) error { |
||||
srcinfo, err := os.Stat(src); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
err = os.MkdirAll(dst, srcinfo.Mode()); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
fds, err := ioutil.ReadDir(src); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, fd := range fds { |
||||
srcfp := path.Join(src, fd.Name()) |
||||
dstfp := path.Join(dst, fd.Name()) |
||||
|
||||
if fd.IsDir() { |
||||
err = CopyDirectory(srcfp, dstfp); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
err = CopyFile(srcfp, dstfp); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func Exists(dst string) bool { |
||||
_, err := os.Stat(dst) |
||||
return !os.IsNotExist(err) |
||||
} |
||||
|
||||
func WriteToFile(dst, value string) error { |
||||
file, err := os.Create(dst) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer file.Close() |
||||
|
||||
writer := bufio.NewWriter(file) |
||||
writer.WriteString(value) |
||||
writer.Flush() |
||||
|
||||
return nil |
||||
} |
@ -1,161 +0,0 @@ |
||||
/* |
||||
* Kosmos Reborn Builder |
||||
* Copyright (C) 2021 Nichole Mattera |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
||||
* 02110-1301, USA. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"io" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"path/filepath" |
||||
"regexp" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
type GitHubUser struct { |
||||
Login string `json:"login"` |
||||
Id int `json:"id"` |
||||
NodeId string `json:"node_id"` |
||||
AvatarUrl string `json:"avatar_url"` |
||||
GravatarId string `json:"gravatar_id"` |
||||
Url string `json:"url"` |
||||
HtmlUrl string `json:"html_url"` |
||||
FollowersUrl string `json:"followers_url"` |
||||
FollowingUrl string `json:"following_url"` |
||||
GistsUrl string `json:"gists_url"` |
||||
StarredUrl string `json:"starred_url"` |
||||
SubscriptionsUrl string `json:"subscriptions_url"` |
||||
OrganizationsUrl string `json:"organizations_url"` |
||||
ReposUrl string `json:"repos_url"` |
||||
EventsUrl string `json:"events_url"` |
||||
ReceivedEventsUrl string `json:"received_events_url"` |
||||
Type string `json:"type"` |
||||
SiteAdmin bool `json:"site_admin"` |
||||
} |
||||
|
||||
type GitHubAsset struct { |
||||
Url string `json:"url"` |
||||
Id int `json:"id"` |
||||
NodeId string `json:"node_id"` |
||||
Name string `json:"name"` |
||||
Label string `json:"label"` |
||||
Uploader GitHubUser `json:"uploader"` |
||||
ContentType string `json:"content_type"` |
||||
State string `json:"state"` |
||||
Size int `json:"size"` |
||||
DownloadCount int `json:"download_count"` |
||||
CreatedAt string `json:"created_at"` |
||||
UpdatedAt string `json:"updated_at"` |
||||
BrowserDownloadUrl string `json:"browser_download_url"` |
||||
} |
||||
|
||||
type GitHubRelease struct { |
||||
Url string `json:"url"` |
||||
AssetsUrl string `json:"assets_url"` |
||||
UploadUrl string `json:"upload_url"` |
||||
HtmlUrl string `json:"html_url"` |
||||
Id int `json:"id"` |
||||
Author GitHubUser `json:"author"` |
||||
NodeId string `json:"node_id"` |
||||
TagName string `json:"tag_name"` |
||||
TargetCommitish string `json:"target_commitish"` |
||||
Name string `json:"name"` |
||||
Draft bool `json:"draft"` |
||||
Prerelease bool `json:"prerelease"` |
||||
CreatedAt string `json:"created_at"` |
||||
PublishedAt string `json:"published_at"` |
||||
Assets []GitHubAsset `json:"assets"` |
||||
TarballUrl string `json:"tarball_url"` |
||||
ZipballUrl string `json:"zipball_url"` |
||||
Body string `json:"body"` |
||||
} |
||||
|
||||
func GetLatestRelease(organization string, repository string, assetPattern string, config Config) (string, string, error) { |
||||
resp, err := http.Get("https://" + config.GithubUsername + ":" + config.GithubPassword + "@api.github.com/repos/" + organization + "/" + repository + "/releases/latest") |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 { |
||||
return "", "", errors.New("Getting latest release returned status code: " + strconv.Itoa(resp.StatusCode)) |
||||
} |
||||
|
||||
defer resp.Body.Close() |
||||
|
||||
body, err := ioutil.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
|
||||
var release GitHubRelease |
||||
json.Unmarshal(body, &release) |
||||
|
||||
for _, asset := range release.Assets { |
||||
matched, err := regexp.Match(assetPattern, []byte(asset.Name)) |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
|
||||
if matched { |
||||
return release.TagName, asset.BrowserDownloadUrl, nil |
||||
} |
||||
} |
||||
|
||||
return "", "", errors.New("No assets") |
||||
} |
||||
|
||||
func DownloadFile(rawUrl string, destination string) (string, error) { |
||||
url, err := url.Parse(rawUrl) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
segments := strings.Split(url.Path, "/") |
||||
fileName := segments[len(segments)-1] |
||||
|
||||
path := filepath.Join(destination, fileName) |
||||
file, err := os.Create(path) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
defer file.Close() |
||||
|
||||
resp, err := http.Get(rawUrl) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 { |
||||
return "", errors.New("Download file returned status code: " + strconv.Itoa(resp.StatusCode)) |
||||
} |
||||
|
||||
defer resp.Body.Close() |
||||
|
||||
_, err = io.Copy(file, resp.Body) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
return path, nil |
||||
} |
@ -1,146 +0,0 @@ |
||||
/* |
||||
* Kosmos Reborn Builder |
||||
* Copyright (C) 2021 Nichole Mattera |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
||||
* 02110-1301, USA. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"archive/zip" |
||||
"errors" |
||||
"io" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
) |
||||
|
||||
type Action string |
||||
const ( |
||||
Copy Action = "copy" |
||||
Delete Action = "delete" |
||||
Extract Action = "extract" |
||||
Mkdir Action = "mkdir" |
||||
) |
||||
|
||||
type Instruction struct { |
||||
Action Action |
||||
Source string |
||||
Destination string |
||||
} |
||||
|
||||
func CopyInstruction(module Module, instruction Instruction, moduleTempDirectory string, tempDirectory string) error { |
||||
matches, err := filepath.Glob(filepath.Join(moduleTempDirectory, instruction.Source)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, match := range matches { |
||||
matchInfo, err := os.Stat(match) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if matchInfo.IsDir() { |
||||
CopyDirectory(match, filepath.Join(tempDirectory, instruction.Destination)) |
||||
} else { |
||||
CopyFile(match, filepath.Join(tempDirectory, instruction.Destination)) |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func DeleteInstruction(module Module, instruction Instruction, moduleTempDirectory string, tempDirectory string) error { |
||||
matches, err := filepath.Glob(filepath.Join(moduleTempDirectory, instruction.Source)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, match := range matches { |
||||
if Exists(match) { |
||||
err = os.Remove(match) |
||||
if err != nil { |
||||
return err |
||||
}
|
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func ExtractInstruction(module Module, instruction Instruction, moduleTempDirectory string, tempDirectory string) error { |
||||
matches, err := filepath.Glob(filepath.Join(moduleTempDirectory, instruction.Source)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(matches) < 1 { |
||||
return errors.New("Nothing to unzip for pattern: " + instruction.Source) |
||||
} |
||||
|
||||
for _, match := range matches { |
||||
zipReader, err := zip.OpenReader(match) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer zipReader.Close() |
||||
|
||||
for _, file := range zipReader.File { |
||||
path := filepath.Join(moduleTempDirectory, file.Name) |
||||
if !strings.HasPrefix(path, filepath.Clean(moduleTempDirectory) + string(os.PathSeparator)) { |
||||
return errors.New("Illegal file path: " + path) |
||||
} |
||||
|
||||
// Extract folder
|
||||
if file.FileInfo().IsDir() { |
||||
os.MkdirAll(path, os.ModePerm) |
||||
continue |
||||
} |
||||
|
||||
// Extract file
|
||||
err = os.MkdirAll(filepath.Dir(path), os.ModePerm); |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
defer outFile.Close() |
||||
|
||||
inFile, err := file.Open() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
defer inFile.Close() |
||||
|
||||
_, err = io.Copy(outFile, inFile) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func MkdirInstruction(module Module, instruction Instruction, moduleTempDirectory string, tempDirectory string) error { |
||||
return os.MkdirAll(filepath.Join(tempDirectory, instruction.Destination), os.ModePerm) |
||||
} |
@ -1,99 +0,0 @@ |
||||
/* |
||||
* Kosmos Reborn Builder |
||||
* Copyright (C) 2021 Nichole Mattera |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
||||
* 02110-1301, USA. |
||||
*/ |
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"io/ioutil" |
||||
"os" |
||||
) |
||||
|
||||
type Module struct { |
||||
Name string |
||||
Org string |
||||
Repo string |
||||
AssetPattern string |
||||
Instructions []Instruction |
||||
} |
||||
|
||||
func BuildModules(tempDirectory string, version string, config Config) (string, error) { |
||||
modules, err := ioutil.ReadDir("./modules") |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
buildMessage := "" |
||||
|
||||
for _, file := range modules { |
||||
moduleFile, err := os.Open("./modules/" + file.Name()) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
defer moduleFile.Close() |
||||
|
||||
byteValue, _ := ioutil.ReadAll(moduleFile) |
||||
|
||||
var module Module |
||||
json.Unmarshal(byteValue, &module) |
||||
|
||||
moduleTempDirectory := GenerateTempPath() |
||||
os.MkdirAll(moduleTempDirectory, os.ModePerm) |
||||
|
||||
version, downloadURL, err := GetLatestRelease(module.Org, module.Repo, module.AssetPattern, config) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
_, err = DownloadFile(downloadURL, moduleTempDirectory) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
for _, instruction := range module.Instructions { |
||||
switch (instruction.Action) { |
||||
case Copy: |
||||
err = CopyInstruction(module, instruction, moduleTempDirectory, tempDirectory) |
||||
break |
||||
|
||||
case Delete: |
||||
err = DeleteInstruction(module, instruction, moduleTempDirectory, tempDirectory) |
||||
break |
||||
|
||||
case Extract: |
||||
err = ExtractInstruction(module, instruction, moduleTempDirectory, tempDirectory) |
||||
break |
||||
|
||||
case Mkdir: |
||||
err = MkdirInstruction(module, instruction, moduleTempDirectory, tempDirectory) |
||||
break |
||||
} |
||||
|
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
} |
||||
|
||||
os.RemoveAll(moduleTempDirectory) |
||||
buildMessage += "\t" + module.Name + ": " + version + "\n" |
||||
} |
||||
|
||||
return buildMessage, nil |
||||
} |
Loading…
Reference in new issue