Live video on the AT Protocol
at next 386 lines 10 kB view raw
1package main 2 3import ( 4 "bytes" 5 "crypto/sha256" 6 "encoding/json" 7 "flag" 8 "fmt" 9 "io" 10 "math/rand" 11 "net/http" 12 "os" 13 "sort" 14 "strings" 15 "text/template" 16 17 "github.com/go-git/go-git/v5" 18 "github.com/go-git/go-git/v5/plumbing" 19 "github.com/go-git/go-git/v5/plumbing/object" 20 "github.com/go-git/go-git/v5/plumbing/storer" 21 "github.com/google/uuid" 22) 23 24func main() { 25 err := makeGit() 26 if err != nil { 27 panic(err) 28 } 29} 30 31var tmpl = `package main 32 33var Version = "%s" 34var BuildTime = "%d" 35var UUID = "%s" 36` 37 38var tmplJS = ` 39export const version = "%s"; 40export const buildTime = "%d"; 41export const uuid = "%s"; 42` 43 44func gitlabURL() string { 45 CI_API_V4_URL := os.Getenv("CI_API_V4_URL") //nolint:all 46 CIProjectID := os.Getenv("CI_PROJECT_ID") 47 CI_API_V4_URL = strings.Replace(CI_API_V4_URL, "https://git.stream.place", "https://git-cloudflare.stream.place", 1) 48 return fmt.Sprintf("%s/projects/%s", CI_API_V4_URL, CIProjectID) 49} 50 51func gitlab(suffix string, dest any) { 52 u := fmt.Sprintf("%s%s", gitlabURL(), suffix) 53 54 req, err := http.Get(u) 55 if err != nil { 56 panic(err) 57 } 58 if err := json.NewDecoder(req.Body).Decode(dest); err != nil { 59 panic(err) 60 } 61} 62 63func gitlabList(suffix string) []map[string]any { 64 var result []map[string]any 65 gitlab(suffix, &result) 66 return result 67} 68 69func gitlabDict(suffix string) map[string]any { 70 var result map[string]any 71 gitlab(suffix, &result) 72 return result 73} 74 75func makeGit() error { 76 output := flag.String("o", "", "file to output to") 77 version := flag.Bool("v", false, "just print version") 78 env := flag.Bool("env", false, "print a bunch of useful environment variables") 79 doBranch := flag.Bool("branch", false, "print branch") 80 doRelease := flag.Bool("release", false, "print release json file") 81 javascript := flag.Bool("js", false, "print code in javascript format") 82 homebrew := flag.Bool("homebrew", false, "print homebrew formula") 83 84 flag.Parse() 85 86 // handle CF_PAGES environment fallback 87 if os.Getenv("CF_PAGES") != "" && *javascript { 88 out := `export const version = "unknown"; export const buildTime = 0; export const uuid = "00000000-0000-0000-0000-000000000000";` 89 if *output != "" { 90 if err := os.WriteFile(*output, []byte(out), 0644); err != nil { 91 return err 92 } 93 } else { 94 fmt.Print(out) 95 } 96 return nil 97 } 98 r, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true}) 99 if err != nil { 100 return err 101 } 102 103 // ... retrieving the HEAD reference 104 ref, err := r.Head() 105 if err != nil { 106 return err 107 } 108 c, err := r.CommitObject(ref.Hash()) 109 if err != nil { 110 return err 111 } 112 113 ts := c.Author.When.Unix() 114 rander := rand.New(rand.NewSource(ts)) 115 u, err := uuid.NewV7FromReader(rander) 116 if err != nil { 117 return err 118 } 119 g, err := PlainOpen(".") 120 if err != nil { 121 return err 122 } 123 desc, err := g.Describe(ref) 124 if err != nil { 125 return err 126 } 127 var out string 128 if *version { 129 out = desc 130 } else if *doBranch { 131 out = branch() 132 } else if *env { 133 StreamplaceBranch := branch() 134 outMap := map[string]string{} 135 outMap["STREAMPLACE_BRANCH"] = StreamplaceBranch 136 outMap["STREAMPLACE_VERSION"] = desc 137 outMap["STREAMPLACE_BRANCH"] = StreamplaceBranch 138 for _, arch := range []string{"amd64", "arm64"} { 139 k := fmt.Sprintf("STREAMPLACE_URL_LINUX_%s", strings.ToUpper(arch)) 140 v := fmt.Sprintf("%s/packages/generic/%s/%s/streamplace-%s-linux-%s.tar.gz", gitlabURL(), StreamplaceBranch, desc, desc, arch) 141 outMap[k] = v 142 macK := fmt.Sprintf("STREAMPLACE_URL_DARWIN_%s", strings.ToUpper(arch)) 143 macV := fmt.Sprintf("%s/packages/generic/%s/%s/streamplace-%s-darwin-%s.zip", gitlabURL(), StreamplaceBranch, desc, desc, arch) 144 outMap[macK] = macV 145 } 146 outMap["STREAMPLACE_DESKTOP_URL_WINDOWS_AMD64"] = fmt.Sprintf("%s/packages/generic/%s/%s/streamplace-desktop-%s-windows-amd64.exe", gitlabURL(), StreamplaceBranch, desc, desc) 147 for k, v := range outMap { 148 out = out + fmt.Sprintf("%s=%s\n", k, v) 149 } 150 } else if *doRelease { 151 outMap := map[string]any{} 152 outMap["name"] = desc 153 outMap["tag-name"] = desc 154 pkgs := gitlabList(fmt.Sprintf("/packages?order_by=created_at&sort=desc&package_name=%s", branch())) 155 id := pkgs[0]["id"].(float64) 156 pkgFiles := gitlabList(fmt.Sprintf("/packages/%d/package_files", int(id))) 157 outFiles := []string{} 158 sort.Slice(pkgFiles, func(i, j int) bool { 159 s1 := pkgFiles[i]["file_name"].(string) 160 s2 := pkgFiles[j]["file_name"].(string) 161 return s1 < s2 162 }) 163 for _, file := range pkgFiles { 164 fileJSON := map[string]string{ 165 "name": file["file_name"].(string), 166 "url": fmt.Sprintf("%s/packages/generic/%s/%s/%s", gitlabURL(), branch(), desc, file["file_name"].(string)), 167 } 168 bs, err := json.Marshal(fileJSON) 169 if err != nil { 170 return err 171 } 172 outFiles = append(outFiles, string(bs)) 173 } 174 outMap["assets-link"] = outFiles 175 changelog := gitlabDict(fmt.Sprintf("/repository/changelog?version=%s", desc)) 176 outMap["description"] = changelog["notes"] 177 bs, err := json.MarshalIndent(outMap, "", " ") 178 if err != nil { 179 return err 180 } 181 out = string(bs) 182 } else if *javascript { 183 out = fmt.Sprintf(tmplJS, desc, ts, u) 184 } else if *homebrew { 185 bs := bytes.Buffer{} 186 versionNoV := strings.TrimPrefix(desc, "v") 187 darwinAmd64File := fmt.Sprintf("streamplace-%s-darwin-amd64.tar.gz", desc) 188 darwinArm64File := fmt.Sprintf("streamplace-%s-darwin-arm64.tar.gz", desc) 189 linuxAmd64File := fmt.Sprintf("streamplace-%s-linux-amd64.tar.gz", desc) 190 linuxArm64File := fmt.Sprintf("streamplace-%s-linux-arm64.tar.gz", desc) 191 192 err = homebrewTmpl.Execute(&bs, Homebrew{ 193 Version: versionNoV, 194 DarwinArm64: getHash(darwinArm64File), 195 DarwinAmd64: getHash(darwinAmd64File), 196 LinuxArm64: getHash(linuxArm64File), 197 LinuxAmd64: getHash(linuxAmd64File), 198 }) 199 if err != nil { 200 return err 201 } 202 out = bs.String() 203 } else { 204 out = fmt.Sprintf(tmpl, desc, ts, u) 205 } 206 207 if *output != "" { 208 if err := os.WriteFile(*output, []byte(out), 0644); err != nil { 209 return err 210 } 211 } else { 212 fmt.Print(out) 213 } 214 return nil 215} 216 217func getHash(fileName string) string { 218 filePath := fmt.Sprintf("bin/%s", fileName) 219 f, err := os.Open(filePath) 220 if err != nil { 221 panic(err) 222 } 223 defer f.Close() 224 225 h := sha256.New() 226 buf := make([]byte, 1024*1024) // 1MB buffer 227 228 for { 229 n, err := f.Read(buf) 230 if n > 0 { 231 if _, err := h.Write(buf[:n]); err != nil { 232 panic(err) 233 } 234 } 235 if err != nil { 236 if err == io.EOF { 237 break 238 } 239 panic(err) 240 } 241 } 242 243 return fmt.Sprintf("%x", h.Sum(nil)) 244} 245 246func branch() string { 247 CICommitTag := os.Getenv("CI_COMMIT_TAG") 248 CICommitBranch := os.Getenv("CI_COMMIT_BRANCH") 249 if CICommitTag != "" { 250 return "latest" 251 } else if CICommitBranch != "" { 252 return strings.ReplaceAll(CICommitBranch, "/", "-") 253 } else { 254 panic("CI_COMMIT_TAG and CI_COMMIT_BRANCH undefined, can't get branch") 255 } 256} 257 258// Git struct wrapps Repository class from go-git to add a tag map used to perform queries when describing. 259type Git struct { 260 TagsMap map[plumbing.Hash]*plumbing.Reference 261 *git.Repository 262} 263 264// PlainOpen opens a git repository from the given path. It detects if the 265// repository is bare or a normal one. If the path doesn't contain a valid 266// repository ErrRepositoryNotExists is returned 267func PlainOpen(path string) (*Git, error) { 268 r, err := git.PlainOpenWithOptions(path, &git.PlainOpenOptions{DetectDotGit: true}) 269 return &Git{ 270 make(map[plumbing.Hash]*plumbing.Reference), 271 r, 272 }, err 273} 274 275func (g *Git) getTagMap() error { 276 tags, err := g.Tags() 277 if err != nil { 278 return err 279 } 280 281 err = tags.ForEach(func(t *plumbing.Reference) error { 282 h, err := g.ResolveRevision(plumbing.Revision(t.Name())) 283 if err != nil { 284 return err 285 } 286 g.TagsMap[*h] = t 287 return nil 288 }) 289 290 return err 291} 292 293// Describe the reference as 'git describe --tags' will do 294func (g *Git) Describe(reference *plumbing.Reference) (string, error) { 295 if os.Getenv("STREAMPLACE_VERSION_OVERRIDE") != "" { 296 return os.Getenv("STREAMPLACE_VERSION_OVERRIDE"), nil 297 } 298 299 // Fetch the reference log 300 cIter, err := g.Log(&git.LogOptions{ 301 // From: reference.Hash(), 302 Order: git.LogOrderCommitterTime, 303 }) 304 if err != nil { 305 return "", err 306 } 307 308 // Build the tag map 309 err = g.getTagMap() 310 if err != nil { 311 return "", err 312 } 313 314 // Search the tag 315 var tag *plumbing.Reference 316 var count int 317 err = cIter.ForEach(func(c *object.Commit) error { 318 t, ok := g.TagsMap[c.Hash] 319 if ok { 320 tag = t 321 return storer.ErrStop 322 } 323 count++ 324 return nil 325 }) 326 if err != nil { 327 return "", err 328 } 329 head, err := g.Head() 330 if err != nil { 331 return "", err 332 } 333 if count == 0 && os.Getenv("CI_COMMIT_TAG") != "" { 334 return fmt.Sprint(tag.Name().Short()), nil 335 } else { 336 return fmt.Sprintf("%s-%s", 337 tag.Name().Short(), 338 head.Hash().String()[0:8], 339 ), nil 340 } 341} 342 343type Homebrew struct { 344 Version string 345 DarwinArm64 string 346 DarwinAmd64 string 347 LinuxArm64 string 348 LinuxAmd64 string 349} 350 351var homebrewTmpl = template.Must(template.New("homebrew").Parse(` 352class Streamplace < Formula 353 desc "Live video for the AT Protocol. Solving video for everybody forever." 354 homepage "https://stream.place" 355 license "GPL-3.0-or-later" 356 version "{{.Version}}" 357 358 on_macos do 359 if Hardware::CPU.arm? 360 url "https://git-cloudflare.stream.place/api/v4/projects/1/packages/generic/latest/v{{.Version}}/streamplace-v{{.Version}}-darwin-arm64.tar.gz" 361 sha256 "{{.DarwinArm64}}" 362 end 363 364 if Hardware::CPU.intel? 365 url "https://git-cloudflare.stream.place/api/v4/projects/1/packages/generic/latest/v{{.Version}}/streamplace-v{{.Version}}-darwin-amd64.tar.gz" 366 sha256 "{{.DarwinAmd64}}" 367 end 368 end 369 370 on_linux do 371 if Hardware::CPU.arm? && Hardware::CPU.is_64_bit? 372 url "https://git-cloudflare.stream.place/api/v4/projects/1/packages/generic/latest/v{{.Version}}/streamplace-v{{.Version}}-linux-arm64.tar.gz" 373 sha256 "{{.LinuxArm64}}" 374 end 375 376 if Hardware::CPU.intel? 377 url "https://git-cloudflare.stream.place/api/v4/projects/1/packages/generic/latest/v{{.Version}}/streamplace-v{{.Version}}-linux-amd64.tar.gz" 378 sha256 "{{.LinuxAmd64}}" 379 end 380 end 381 382 def install 383 bin.install "streamplace" => "streamplace" 384 end 385end 386`))