// Package main contains an utility to get the server version package main import ( "fmt" "log" "os" "path/filepath" "strconv" "strings" "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/cache" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/storage/filesystem" ) // Golang version of git describe --tags func gitDescribeTags(repo *git.Repository) (string, error) { head, err := repo.Head() if err != nil { return "", fmt.Errorf("failed to get HEAD: %w", err) } tagIterator, err := repo.Tags() if err != nil { return "", fmt.Errorf("failed to get tags: %w", err) } defer tagIterator.Close() tags := make(map[plumbing.Hash]*plumbing.Reference) err = tagIterator.ForEach(func(t *plumbing.Reference) error { if to, err2 := repo.TagObject(t.Hash()); err2 == nil { tags[to.Target] = t } else { tags[t.Hash()] = t } return nil }) if err != nil { return "", fmt.Errorf("failed to iterate tags: %w", err) } cIter, err := repo.Log(&git.LogOptions{From: head.Hash()}) if err != nil { return "", fmt.Errorf("failed to get log: %w", err) } i := 0 for { var commit *object.Commit commit, err = cIter.Next() if err != nil { return "", fmt.Errorf("failed to get next commit: %w", err) } if str, ok := tags[commit.Hash]; ok { label := strings.TrimPrefix(string(str.Name()), "refs/tags/") if i != 0 { label += "-" + strconv.FormatInt(int64(i), 10) + "-" + head.Hash().String()[:8] } return label, nil } i++ } } func tagFromGit() error { // [git.PlainOpen] uses a ChrootOS that limits filesystem access to the .git directory only. // // Unfortunately, this can cause issues with package build environments such as Arch Linux's, // where .git/objects/info/alternates points to a directory outside of the .git directory. // // To work around this, specify an AlternatesFS that allows access to the entire filesystem. dotGitAbs, _ := filepath.Abs("../../.git") storerFs := osfs.New(dotGitAbs, osfs.WithBoundOS()) storer := filesystem.NewStorageWithOptions(storerFs, cache.NewObjectLRUDefault(), filesystem.Options{ AlternatesFS: osfs.New("/", osfs.WithBoundOS()), }) workTreeAbs, _ := filepath.Abs("../../") worktreeFs := osfs.New(workTreeAbs, osfs.WithBoundOS()) repo, err := git.Open(storer, worktreeFs) if err != nil { return fmt.Errorf("failed to open repository: %w", err) } version, err := gitDescribeTags(repo) if err != nil { return fmt.Errorf("failed to get version: %w", err) } wt, err := repo.Worktree() if err != nil { return fmt.Errorf("failed to get worktree: %w", err) } status, err := wt.Status() if err != nil { return fmt.Errorf("failed to get status: %w", err) } if !status.IsClean() { version += "-dirty" } err = os.WriteFile("VERSION", []byte(version), 0o644) if err != nil { return fmt.Errorf("failed to write version file: %w", err) } log.Printf("ok (%s)", version) return nil } func do() error { log.Println("getting mediamtx version...") err := tagFromGit() if err != nil { log.Println("WARN: cannot get tag from .git folder, using v0.0.0 as version") err = os.WriteFile("VERSION", []byte("v0.0.0"), 0o644) if err != nil { return fmt.Errorf("failed to write version file: %w", err) } } return nil } func main() { err := do() if err != nil { log.Printf("ERR: %v", err) os.Exit(1) } }