Add build version/time stamp to Setup page and awp-cli version command

Embeds the git short hash and UTC build timestamp into both binaries
via ldflags so it's easy to confirm which build is actually deployed,
without needing to hand-maintain a version number.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Harry Culpan 2026-07-11 19:33:40 -04:00
parent 7370d1291e
commit a7ebc192c8
5 changed files with 80 additions and 14 deletions

View file

@ -1,14 +1,18 @@
VERSION := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
BUILDTIME := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X github.com/hculpan/areweplaying/pkg/version.Version=$(VERSION) -X github.com/hculpan/areweplaying/pkg/version.BuildTime=$(BUILDTIME)
all: build all: build
build: build:
templ generate templ generate
go build -o areweplaying cmd/web/*.go go build -ldflags "$(LDFLAGS)" -o areweplaying cmd/web/*.go
go build -o awp-cli cmd/cli/*.go go build -ldflags "$(LDFLAGS)" -o awp-cli cmd/cli/*.go
linuxbuild: linuxbuild:
templ generate templ generate
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o areweplaying.linux cmd/web/*.go CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o areweplaying.linux cmd/web/*.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o awp-cli.linux cmd/cli/*.go CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o awp-cli.linux cmd/cli/*.go
clean: clean:
rm -rf areweplaying rm -rf areweplaying

25
cmd/cli/cmd/version.go Normal file
View file

@ -0,0 +1,25 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/hculpan/areweplaying/pkg/version"
"github.com/spf13/cobra"
)
// versionCmd represents the version command
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the build version and time",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Printf("awp-cli %s (built %s)\n", version.Version, version.BuildTime)
return nil
},
}
func init() {
rootCmd.AddCommand(versionCmd)
}

View file

@ -3,6 +3,7 @@ package templates
import ( import (
"strconv" "strconv"
"github.com/hculpan/areweplaying/pkg/data" "github.com/hculpan/areweplaying/pkg/data"
"github.com/hculpan/areweplaying/pkg/version"
) )
templ Setup(session data.Session) { templ Setup(session data.Session) {
@ -197,7 +198,8 @@ templ Setup(session data.Session) {
} }
</script> </script>
<footer class=" container"> <footer class="container text-muted small mt-4">
Build: { version.Version } ({ version.BuildTime })
</footer> </footer>
</body> </body>

View file

@ -10,6 +10,7 @@ import templruntime "github.com/a-h/templ/runtime"
import ( import (
"github.com/hculpan/areweplaying/pkg/data" "github.com/hculpan/areweplaying/pkg/data"
"github.com/hculpan/areweplaying/pkg/version"
"strconv" "strconv"
) )
@ -49,7 +50,7 @@ func Setup(session data.Session) templ.Component {
var templ_7745c5c3_Var2 string var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(strconv.Itoa(session.IncrementDays)) templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(strconv.Itoa(session.IncrementDays))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 48, Col: 103} return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 49, Col: 103}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -67,7 +68,7 @@ func Setup(session data.Session) templ.Component {
var templ_7745c5c3_Var3 string var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(p.Name) templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(p.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 89, Col: 44} return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 90, Col: 44}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -80,7 +81,7 @@ func Setup(session data.Session) templ.Component {
var templ_7745c5c3_Var4 string var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(p.Email) templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(p.Email)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 90, Col: 45} return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 91, Col: 45}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -103,7 +104,7 @@ func Setup(session data.Session) templ.Component {
var templ_7745c5c3_Var5 string var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Name) templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 100, Col: 58} return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 101, Col: 58}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -116,7 +117,7 @@ func Setup(session data.Session) templ.Component {
var templ_7745c5c3_Var6 string var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Email) templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Email)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 101, Col: 60} return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 102, Col: 60}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -129,7 +130,7 @@ func Setup(session data.Session) templ.Component {
var templ_7745c5c3_Var7 string var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(strconv.FormatBool(p.Gm)) templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(strconv.FormatBool(p.Gm))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 102, Col: 74} return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 103, Col: 74}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -142,7 +143,7 @@ func Setup(session data.Session) templ.Component {
var templ_7745c5c3_Var8 string var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Attending) templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Attending)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 103, Col: 68} return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 104, Col: 68}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -155,7 +156,7 @@ func Setup(session data.Session) templ.Component {
var templ_7745c5c3_Var9 string var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Name) templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(p.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 108, Col: 87} return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 109, Col: 87}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -166,7 +167,33 @@ func Setup(session data.Session) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</tbody></table></div></div></div><!-- Add Player Modal --><div class=\"modal fade\" id=\"addPlayerModal\" tabindex=\"-1\" aria-labelledby=\"addPlayerModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content bg-dark text-light\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"addPlayerModalLabel\">Add Player</h5><button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form action=\"/admin/players/add\" method=\"post\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"addName\" class=\"form-label\">Name</label> <input type=\"text\" class=\"form-control\" id=\"addName\" name=\"name\" required></div><div class=\"mb-3\"><label for=\"addEmail\" class=\"form-label\">Email</label> <input type=\"email\" class=\"form-control\" id=\"addEmail\" name=\"email\" required></div><div class=\"form-check\"><input class=\"form-check-input\" type=\"checkbox\" id=\"addGm\" name=\"gm\"> <label class=\"form-check-label\" for=\"addGm\">Game Master</label></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Add Player</button></div></form></div></div></div><!-- Edit Player Modal --><div class=\"modal fade\" id=\"editPlayerModal\" tabindex=\"-1\" aria-labelledby=\"editPlayerModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content bg-dark text-light\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"editPlayerModalLabel\">Edit Player</h5><button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form action=\"/admin/players/edit\" method=\"post\"><input type=\"hidden\" id=\"editOriginalName\" name=\"originalName\"> <input type=\"hidden\" id=\"editAttending\" name=\"attending\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"editName\" class=\"form-label\">Name</label> <input type=\"text\" class=\"form-control\" id=\"editName\" name=\"name\" required></div><div class=\"mb-3\"><label for=\"editEmail\" class=\"form-label\">Email</label> <input type=\"email\" class=\"form-control\" id=\"editEmail\" name=\"email\" required></div><div class=\"form-check\"><input class=\"form-check-input\" type=\"checkbox\" id=\"editGm\" name=\"gm\"> <label class=\"form-check-label\" for=\"editGm\">Game Master</label></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Save Changes</button></div></form></div></div></div><script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js\" integrity=\"sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM\" crossorigin=\"anonymous\"></script><script>\n function populateEditModal(btn) {\n document.getElementById('editOriginalName').value = btn.dataset.name;\n document.getElementById('editName').value = btn.dataset.name;\n document.getElementById('editEmail').value = btn.dataset.email;\n document.getElementById('editGm').checked = btn.dataset.gm === 'true';\n document.getElementById('editAttending').value = btn.dataset.attending;\n }\n </script><footer class=\"container\"></footer></body></html>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</tbody></table></div></div></div><!-- Add Player Modal --><div class=\"modal fade\" id=\"addPlayerModal\" tabindex=\"-1\" aria-labelledby=\"addPlayerModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content bg-dark text-light\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"addPlayerModalLabel\">Add Player</h5><button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form action=\"/admin/players/add\" method=\"post\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"addName\" class=\"form-label\">Name</label> <input type=\"text\" class=\"form-control\" id=\"addName\" name=\"name\" required></div><div class=\"mb-3\"><label for=\"addEmail\" class=\"form-label\">Email</label> <input type=\"email\" class=\"form-control\" id=\"addEmail\" name=\"email\" required></div><div class=\"form-check\"><input class=\"form-check-input\" type=\"checkbox\" id=\"addGm\" name=\"gm\"> <label class=\"form-check-label\" for=\"addGm\">Game Master</label></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Add Player</button></div></form></div></div></div><!-- Edit Player Modal --><div class=\"modal fade\" id=\"editPlayerModal\" tabindex=\"-1\" aria-labelledby=\"editPlayerModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content bg-dark text-light\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"editPlayerModalLabel\">Edit Player</h5><button type=\"button\" class=\"btn-close btn-close-white\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form action=\"/admin/players/edit\" method=\"post\"><input type=\"hidden\" id=\"editOriginalName\" name=\"originalName\"> <input type=\"hidden\" id=\"editAttending\" name=\"attending\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"editName\" class=\"form-label\">Name</label> <input type=\"text\" class=\"form-control\" id=\"editName\" name=\"name\" required></div><div class=\"mb-3\"><label for=\"editEmail\" class=\"form-label\">Email</label> <input type=\"email\" class=\"form-control\" id=\"editEmail\" name=\"email\" required></div><div class=\"form-check\"><input class=\"form-check-input\" type=\"checkbox\" id=\"editGm\" name=\"gm\"> <label class=\"form-check-label\" for=\"editGm\">Game Master</label></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\">Save Changes</button></div></form></div></div></div><script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js\" integrity=\"sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM\" crossorigin=\"anonymous\"></script><script>\n function populateEditModal(btn) {\n document.getElementById('editOriginalName').value = btn.dataset.name;\n document.getElementById('editName').value = btn.dataset.name;\n document.getElementById('editEmail').value = btn.dataset.email;\n document.getElementById('editGm').checked = btn.dataset.gm === 'true';\n document.getElementById('editAttending').value = btn.dataset.attending;\n }\n </script><footer class=\"container text-muted small mt-4\">Build: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(version.Version)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 202, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, " (")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(version.BuildTime)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cmd/web/templates/setup.templ`, Line: 202, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, ")</footer></body></html>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

8
pkg/version/version.go Normal file
View file

@ -0,0 +1,8 @@
package version
// Set at build time via -ldflags (see Makefile). Defaults are used for
// `go run` / unbuilt binaries.
var (
Version = "dev"
BuildTime = "unknown"
)