// Command ci-release uploads a new launcher binary to the server. // // Usage: // // ci-release \ // --url https://minecraft.mrixs.me \ // --token \ // --version 1.2.0 \ // --os windows \ // --arch amd64 \ // --file ./build/launcher-windows-amd64.exe // // SHA-256 is computed automatically from the file. package main import ( "bytes" "crypto/sha256" "encoding/hex" "flag" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" ) func main() { var ( serverURL = flag.String("url", "", "Server base URL (e.g. https://minecraft.mrixs.me)") token = flag.String("token", "", "CI token (from CI_SECRET)") version = flag.String("version", "", "Launcher version (e.g. 1.2.0)") osParam = flag.String("os", "", "Target OS: windows, linux, darwin") arch = flag.String("arch", "", "Target arch: amd64, arm64") filePath = flag.String("file", "", "Path to launcher binary") ) flag.Parse() // Validate required flags. missing := false for _, f := range []struct{ name, value string }{ {"--url", *serverURL}, {"--token", *token}, {"--version", *version}, {"--os", *osParam}, {"--arch", *arch}, {"--file", *filePath}, } { if f.value == "" { fmt.Fprintf(os.Stderr, "ERROR: missing required flag %s\n", f.name) missing = true } } if missing { flag.Usage() os.Exit(1) } // Read the binary. data, err := os.ReadFile(*filePath) if err != nil { fmt.Fprintf(os.Stderr, "ERROR reading file: %v\n", err) os.Exit(1) } // Compute SHA-256. hash := sha256.Sum256(data) sha256hex := hex.EncodeToString(hash[:]) fmt.Printf("Uploading %s (%s/%s) v%s (%d bytes, sha256=%s)\n", filepath.Base(*filePath), *osParam, *arch, *version, len(data), sha256hex) // Build multipart form. var body bytes.Buffer w := multipart.NewWriter(&body) // Text fields. for _, f := range []struct{ name, value string }{ {"version", *version}, {"os", *osParam}, {"arch", *arch}, {"sha256", sha256hex}, } { if err := w.WriteField(f.name, f.value); err != nil { fmt.Fprintf(os.Stderr, "ERROR building form: %v\n", err) os.Exit(1) } } // File part. fw, err := w.CreateFormFile("file", filepath.Base(*filePath)) if err != nil { fmt.Fprintf(os.Stderr, "ERROR creating form file: %v\n", err) os.Exit(1) } if _, err := fw.Write(data); err != nil { fmt.Fprintf(os.Stderr, "ERROR writing file data: %v\n", err) os.Exit(1) } w.Close() // Send request. url := *serverURL + "/api/admin/launcher/release" req, err := http.NewRequest("POST", url, &body) if err != nil { fmt.Fprintf(os.Stderr, "ERROR creating request: %v\n", err) os.Exit(1) } req.Header.Set("Content-Type", w.FormDataContentType()) req.Header.Set("X-CI-Token", *token) resp, err := http.DefaultClient.Do(req) if err != nil { fmt.Fprintf(os.Stderr, "ERROR sending request: %v\n", err) os.Exit(1) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) fmt.Printf("HTTP %s: %s\n", resp.Status, string(respBody)) if resp.StatusCode != http.StatusCreated { os.Exit(1) } fmt.Println("✓ Release uploaded successfully.") }