From 3ed43bda8b32a4fa78dd748c25616eaab9ceac42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20Bu=CC=88hrig?= Date: Fri, 11 Jul 2025 09:53:35 +0200 Subject: [PATCH] Update Dagger pipeline and documentation for Gitea release upload and .env handling --- .gitignore | 5 +- README.md | 20 +++++- ci/.env.example | 4 ++ ci/dagger.go | 164 +++++++++++++++++++++++++++++++++++++++++++++++- go.mod | 1 + go.sum | 2 + 6 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 ci/.env.example diff --git a/.gitignore b/.gitignore index 737db5d..8a605ec 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,7 @@ Thumbs.db ehthumbs.db # Dagger cache -.dagger/ \ No newline at end of file +.dagger/ + +# Environment files +.env \ No newline at end of file diff --git a/README.md b/README.md index 206c38e..76e6a8e 100644 --- a/README.md +++ b/README.md @@ -218,19 +218,35 @@ The pipeline is defined in [`ci/dagger.go`](./ci/dagger.go). It outputs binaries ### Usage -1. Install the Dagger Go SDK: +1. Install the Dagger Go SDK and dependencies: ```sh go install dagger.io/dagger@latest + go get github.com/joho/godotenv go mod tidy ``` -2. Run the pipeline: +2. Build for all platforms: ```sh go run ci/dagger.go ``` +3. (Optional) Upload binaries to a Gitea release: + - Create a `.env` file in the project root with: + ``` + GITEA_TOKEN=your_token + GITEA_URL=https://your.gitea.server + GITEA_OWNER=youruser + GITEA_REPO=yourrepo + ``` + - Run: + ```sh + go run ci/dagger.go --release v1.0.0 + ``` + - This will create (if needed) and upload all binaries to the specified release tag on your Gitea instance. + - **The pipeline will also create and push a local git tag for the release if it does not already exist.** ### Troubleshooting Dagger - If you see `could not import dagger.io/dagger`, make sure you have installed the Dagger Go SDK and run `go mod tidy`. - The pipeline requires Docker or a compatible container runtime. +- For Gitea upload, ensure your `.env` file is present and correct. ## Troubleshooting diff --git a/ci/.env.example b/ci/.env.example new file mode 100644 index 0000000..90ea473 --- /dev/null +++ b/ci/.env.example @@ -0,0 +1,4 @@ +GITEA_TOKEN=your_token +GITEA_URL=https://your.gitea.server +GITEA_OWNER=youruser +GITEA_REPO=yourrepo \ No newline at end of file diff --git a/ci/dagger.go b/ci/dagger.go index 289a1bf..17e8308 100644 --- a/ci/dagger.go +++ b/ci/dagger.go @@ -1,16 +1,35 @@ // Dagger pipeline for multi-platform builds. -// Requires: go install dagger.io/dagger@latest && go mod tidy +// Usage: +// +// go run ci/dagger.go [--release v1.0.0] +// +// If --release is provided, uploads all built binaries in dist/ to the specified Gitea release. +// Requires .env file with GITEA_TOKEN, GITEA_URL, GITEA_OWNER, GITEA_REPO. +// Requires: go install dagger.io/dagger@latest && go get github.com/joho/godotenv && go mod tidy package main import ( + "bytes" "context" + "encoding/json" + "flag" "fmt" + "io" + "mime/multipart" + "net/http" "os" + "os/exec" + "path/filepath" + "strings" "dagger.io/dagger" + "github.com/joho/godotenv" ) func main() { + releaseTag := flag.String("release", "", "(optional) Release tag to upload binaries to Gitea") + flag.Parse() + ctx := context.Background() client, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stdout)) if err != nil { @@ -30,6 +49,8 @@ func main() { src := client.Host().Directory(".") + var builtBinaries []string + for _, p := range platforms { binName := fmt.Sprintf("valhallir-deconvolver-%s-%s", p.OS, p.Arch) if p.OS == "windows" { @@ -50,5 +71,146 @@ func main() { panic(fmt.Sprintf("export failed for %s/%s: %v", p.OS, p.Arch, err)) } fmt.Printf("Built and exported %s\n", outPath) + builtBinaries = append(builtBinaries, outPath) + } + + if *releaseTag != "" { + fmt.Printf("\nUploading binaries to Gitea release: %s\n", *releaseTag) + // Load .env + err := godotenv.Load("ci/.env") + if err != nil { + panic("Error loading .env file: " + err.Error()) + } + giteaToken := os.Getenv("GITEA_TOKEN") + giteaURL := os.Getenv("GITEA_URL") + giteaOwner := os.Getenv("GITEA_OWNER") + giteaRepo := os.Getenv("GITEA_REPO") + if giteaToken == "" || giteaURL == "" || giteaOwner == "" || giteaRepo == "" { + panic("GITEA_TOKEN, GITEA_URL, GITEA_OWNER, GITEA_REPO must be set in .env") + } + + // 1. Get or create the release + releaseID, err := getOrCreateRelease(giteaURL, giteaOwner, giteaRepo, *releaseTag, giteaToken) + if err != nil { + panic("Failed to get or create release: " + err.Error()) + } + + // 2. Upload each binary as an asset + for _, bin := range builtBinaries { + fmt.Printf("Uploading %s...\n", bin) + err := uploadAsset(giteaURL, giteaOwner, giteaRepo, releaseID, bin, giteaToken) + if err != nil { + panic(fmt.Sprintf("Failed to upload %s: %v", bin, err)) + } + fmt.Printf("Uploaded %s\n", bin) + } + fmt.Println("All binaries uploaded to Gitea release.") + + // 3. Tag the release locally and push the tag + if err := tagAndPush(*releaseTag); err != nil { + panic("Failed to tag and push: " + err.Error()) + } + fmt.Printf("Tagged and pushed %s\n", *releaseTag) } } + +func getOrCreateRelease(url, owner, repo, tag, token string) (int, error) { + // Try to get the release by tag + api := fmt.Sprintf("%s/api/v1/repos/%s/%s/releases/tags/%s", strings.TrimRight(url, "/"), owner, repo, tag) + req, _ := http.NewRequest("GET", api, nil) + req.Header.Set("Authorization", "token "+token) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return 0, err + } + defer resp.Body.Close() + if resp.StatusCode == 200 { + // Parse JSON to get ID + type releaseResp struct{ ID int } + var r releaseResp + io.ReadAll(resp.Body) // ignore body for now, parse below + dec := json.NewDecoder(resp.Body) + dec.Decode(&r) + return r.ID, nil + } + // If not found, create it + if resp.StatusCode == 404 { + api = fmt.Sprintf("%s/api/v1/repos/%s/%s/releases", strings.TrimRight(url, "/"), owner, repo) + body := strings.NewReader(fmt.Sprintf(`{"tag_name":"%s","name":"%s"}`, tag, tag)) + req, _ = http.NewRequest("POST", api, body) + req.Header.Set("Authorization", "token "+token) + req.Header.Set("Content-Type", "application/json") + resp, err = http.DefaultClient.Do(req) + if err != nil { + return 0, err + } + defer resp.Body.Close() + if resp.StatusCode == 201 { + type releaseResp struct{ ID int } + var r releaseResp + dec := json.NewDecoder(resp.Body) + dec.Decode(&r) + return r.ID, nil + } + return 0, fmt.Errorf("failed to create release: %s", resp.Status) + } + return 0, fmt.Errorf("failed to get release: %s", resp.Status) +} + +func uploadAsset(url, owner, repo string, releaseID int, filePath, token string) error { + api := fmt.Sprintf("%s/api/v1/repos/%s/%s/releases/%d/assets?name=%s", strings.TrimRight(url, "/"), owner, repo, releaseID, filepath.Base(filePath)) + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + + var b bytes.Buffer + w := multipart.NewWriter(&b) + f, err := w.CreateFormFile("attachment", filepath.Base(filePath)) + if err != nil { + return err + } + _, err = io.Copy(f, file) + if err != nil { + return err + } + w.Close() + + req, _ := http.NewRequest("POST", api, &b) + req.Header.Set("Authorization", "token "+token) + req.Header.Set("Content-Type", w.FormDataContentType()) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != 201 { + return fmt.Errorf("upload failed: %s", resp.Status) + } + return nil +} + +func tagAndPush(tag string) error { + // Check if tag exists + cmd := exec.Command("git", "tag", "--list", tag) + out, err := cmd.Output() + if err != nil { + return err + } + if strings.TrimSpace(string(out)) == tag { + // Tag already exists + return nil + } + // Create tag + cmd = exec.Command("git", "tag", tag) + if err := cmd.Run(); err != nil { + return err + } + // Push tag + cmd = exec.Command("git", "push", "origin", tag) + if err := cmd.Run(); err != nil { + return err + } + return nil +} diff --git a/go.mod b/go.mod index 296f320..f6698e6 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( dagger.io/dagger v0.18.12 github.com/go-audio/audio v1.0.0 github.com/go-audio/wav v1.1.0 + github.com/joho/godotenv v1.5.1 github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 github.com/urfave/cli/v2 v2.27.7 gonum.org/v1/gonum v0.13.0 diff --git a/go.sum b/go.sum index 526ac18..b289d99 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mjibson/go-dsp v0.0.0-20180508042940-11479a337f12 h1:dd7vnTDfjtwCETZDrRe+GPYNLA1jBtbZeyfyE8eZCyk=