mirror of
https://github.com/lunchcat/sif.git
synced 2026-06-12 11:01:24 -07:00
feat: stamp and surface the build version
- add internal/version: resolve from the release ldflag, else the go build info (module tag / vcs revision), else "dev" - show the version on the boot banner and for `sif version` - Makefile now stamps `make` builds via git describe (matching the release ci), so local/go-install builds report a real version instead of "dev" - patchnotes.ShowOnce skips pseudo/dev versions so non-release builds dont make a doomed github call - document sif version / sif patchnote / SIF_NO_PATCHNOTES in the readme + usage
This commit is contained in:
@@ -11,6 +11,11 @@ PREFIX ?= /usr/local
|
|||||||
BINDIR ?= bin
|
BINDIR ?= bin
|
||||||
MANDIR ?= share/man/man1
|
MANDIR ?= share/man/man1
|
||||||
|
|
||||||
|
# stamp local builds with the nearest v* tag (or short sha), matching the
|
||||||
|
# release ci. --match keeps the automated-release-* tags out of the version.
|
||||||
|
VERSION ?= $(shell git describe --tags --match 'v*' --always --dirty 2>/dev/null | sed 's/^v//')
|
||||||
|
GO_LDFLAGS = -X main.version=$(VERSION)
|
||||||
|
|
||||||
define COPYRIGHT_ASCII
|
define COPYRIGHT_ASCII
|
||||||
╭────────────────────────────────────────────────────────────╮
|
╭────────────────────────────────────────────────────────────╮
|
||||||
│ _____________ │
|
│ _____________ │
|
||||||
@@ -57,7 +62,7 @@ sif: check_go_version
|
|||||||
@echo "📁 Current directory: $$(pwd)"
|
@echo "📁 Current directory: $$(pwd)"
|
||||||
@echo "🔧 Go flags: $(GOFLAGS)"
|
@echo "🔧 Go flags: $(GOFLAGS)"
|
||||||
@echo "📦 Building package: ./cmd/sif"
|
@echo "📦 Building package: ./cmd/sif"
|
||||||
$(GO) build -v $(GOFLAGS) ./cmd/sif
|
$(GO) build -v $(GOFLAGS) -ldflags "$(GO_LDFLAGS)" ./cmd/sif
|
||||||
@echo "📊 Build info:"
|
@echo "📊 Build info:"
|
||||||
@$(GO) version -m sif
|
@$(GO) version -m sif
|
||||||
@echo "✅ sif built successfully! 🚀"
|
@echo "✅ sif built successfully! 🚀"
|
||||||
|
|||||||
@@ -131,6 +131,20 @@ makepkg -si
|
|||||||
|
|
||||||
run `./sif -h` for all options.
|
run `./sif -h` for all options.
|
||||||
|
|
||||||
|
## commands
|
||||||
|
|
||||||
|
a couple of subcommands run without scanning:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# print the version (release builds are stamped; local builds use git describe)
|
||||||
|
./sif version
|
||||||
|
|
||||||
|
# show the latest release notes (also -pn)
|
||||||
|
./sif patchnote
|
||||||
|
```
|
||||||
|
|
||||||
|
the first time you run a new release, sif prints that release's notes once. set `SIF_NO_PATCHNOTES=1` to turn that off.
|
||||||
|
|
||||||
## modules
|
## modules
|
||||||
|
|
||||||
sif has a modular architecture. modules are defined in yaml and can be extended by users.
|
sif has a modular architecture. modules are defined in yaml and can be extended by users.
|
||||||
|
|||||||
+6
-1
@@ -20,15 +20,20 @@ import (
|
|||||||
"github.com/dropalldatabases/sif"
|
"github.com/dropalldatabases/sif"
|
||||||
"github.com/dropalldatabases/sif/internal/config"
|
"github.com/dropalldatabases/sif/internal/config"
|
||||||
"github.com/dropalldatabases/sif/internal/patchnotes"
|
"github.com/dropalldatabases/sif/internal/patchnotes"
|
||||||
|
ver "github.com/dropalldatabases/sif/internal/version"
|
||||||
|
|
||||||
// Register framework detectors
|
// Register framework detectors
|
||||||
_ "github.com/dropalldatabases/sif/internal/scan/frameworks/detectors"
|
_ "github.com/dropalldatabases/sif/internal/scan/frameworks/detectors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// version is filled in at release time via -ldflags "-X main.version=...".
|
// version is stamped at release time via -ldflags "-X main.version=...";
|
||||||
|
// ver.Resolve falls back to the build info or "dev" for other builds.
|
||||||
var version = "dev"
|
var version = "dev"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
version = ver.Resolve(version)
|
||||||
|
sif.Version = version
|
||||||
|
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
case "patchnote", "patchnotes", "-pn", "--patchnotes":
|
case "patchnote", "patchnotes", "-pn", "--patchnotes":
|
||||||
|
|||||||
@@ -259,6 +259,28 @@ enable api mode for json output:
|
|||||||
|
|
||||||
output is a json object with scan results.
|
output is a json object with scan results.
|
||||||
|
|
||||||
|
## commands
|
||||||
|
|
||||||
|
these run without scanning a target.
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
print the sif version. release builds are stamped via ldflags, local `make` builds derive it from `git describe`, and `go install`ed builds read it from the module build info:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./sif version
|
||||||
|
```
|
||||||
|
|
||||||
|
### patchnote
|
||||||
|
|
||||||
|
show the latest release's notes, fetched from github (also `-pn`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./sif patchnote
|
||||||
|
```
|
||||||
|
|
||||||
|
the first time you run a new release sif also prints that release's notes once. set `SIF_NO_PATCHNOTES=1` to disable that.
|
||||||
|
|
||||||
## examples
|
## examples
|
||||||
|
|
||||||
### quick recon
|
### quick recon
|
||||||
|
|||||||
@@ -98,7 +98,9 @@ func Print(tag string) {
|
|||||||
// then records it so it isn't shown again. best-effort: dev builds, the
|
// then records it so it isn't shown again. best-effort: dev builds, the
|
||||||
// SIF_NO_PATCHNOTES opt-out, and any network failure stay silent.
|
// SIF_NO_PATCHNOTES opt-out, and any network failure stay silent.
|
||||||
func ShowOnce(version string) {
|
func ShowOnce(version string) {
|
||||||
if version == "" || version == "dev" || os.Getenv("SIF_NO_PATCHNOTES") != "" {
|
// only clean release tags (e.g. 2026.6.7) map to a github release; skip dev
|
||||||
|
// and pseudo-versions (a commit/dirty build) so we don't make a doomed call.
|
||||||
|
if version == "" || version == "dev" || strings.ContainsAny(version, "-+") || os.Getenv("SIF_NO_PATCHNOTES") != "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||||
|
: :
|
||||||
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||||
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||||
|
: :
|
||||||
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
||||||
|
: lunchcat alumni & contributors :
|
||||||
|
: :
|
||||||
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package version resolves sif's version from the build.
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resolve returns the best version available: the build-time ldflag if it was
|
||||||
|
// stamped, else the go build info (module tag or vcs revision), else "dev". the
|
||||||
|
// leading v is dropped so it matches the bare form the rest of sif uses.
|
||||||
|
func Resolve(ldflag string) string {
|
||||||
|
if ldflag != "" && ldflag != "dev" {
|
||||||
|
return normalize(ldflag)
|
||||||
|
}
|
||||||
|
if v := fromBuildInfo(); v != "" {
|
||||||
|
return normalize(v)
|
||||||
|
}
|
||||||
|
return "dev"
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromBuildInfo() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if v := info.Main.Version; v != "" && v != "(devel)" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// no module tag (a local build) - fall back to the commit it was built from
|
||||||
|
var revision, modified string
|
||||||
|
for _, s := range info.Settings {
|
||||||
|
switch s.Key {
|
||||||
|
case "vcs.revision":
|
||||||
|
revision = s.Value
|
||||||
|
case "vcs.modified":
|
||||||
|
modified = s.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if revision == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(revision) > 12 {
|
||||||
|
revision = revision[:12]
|
||||||
|
}
|
||||||
|
if modified == "true" {
|
||||||
|
revision += "-dirty"
|
||||||
|
}
|
||||||
|
return revision
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalize(v string) string {
|
||||||
|
return strings.TrimPrefix(v, "v")
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||||
|
: :
|
||||||
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||||
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||||
|
: :
|
||||||
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
||||||
|
: lunchcat alumni & contributors :
|
||||||
|
: :
|
||||||
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||||
|
*/
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestResolveLdflag(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
ldflag string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"tag with v", "v2026.6.7", "2026.6.7"},
|
||||||
|
{"tag without v", "2026.6.7", "2026.6.7"},
|
||||||
|
{"pseudo version", "2026.2.17-57-geb33321", "2026.2.17-57-geb33321"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := Resolve(tt.ldflag); got != tt.want {
|
||||||
|
t.Errorf("Resolve(%q) = %q, want %q", tt.ldflag, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// with no ldflag, Resolve falls back to build info; in a test binary that's
|
||||||
|
// non-deterministic, so just assert it never returns an empty string.
|
||||||
|
func TestResolveFallbackNonEmpty(t *testing.T) {
|
||||||
|
if Resolve("dev") == "" {
|
||||||
|
t.Error("Resolve fallback should never be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,9 @@ type App struct {
|
|||||||
logFiles []string
|
logFiles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Version is set by main to the resolved build version and shown on the banner.
|
||||||
|
var Version = "dev"
|
||||||
|
|
||||||
type UrlResult struct {
|
type UrlResult struct {
|
||||||
Url string `json:"url"`
|
Url string `json:"url"`
|
||||||
Results []ModuleResult
|
Results []ModuleResult
|
||||||
@@ -76,7 +79,11 @@ func New(settings *config.Settings) (*App, error) {
|
|||||||
|
|
||||||
if !settings.ApiMode {
|
if !settings.ApiMode {
|
||||||
fmt.Println(output.Box.Render(" █▀ █ █▀▀\n ▄█ █ █▀ "))
|
fmt.Println(output.Box.Render(" █▀ █ █▀▀\n ▄█ █ █▀ "))
|
||||||
fmt.Println(output.Subheading.Render("\nblazing-fast pentesting suite\n\nbsd 3-clause · (c) 2022-2026 vmfunc, xyzeva & contributors\n"))
|
tagline := "blazing-fast pentesting suite"
|
||||||
|
if Version != "dev" {
|
||||||
|
tagline += " · v" + Version
|
||||||
|
}
|
||||||
|
fmt.Println(output.Subheading.Render("\n" + tagline + "\n\nbsd 3-clause · (c) 2022-2026 vmfunc, xyzeva & contributors\n"))
|
||||||
} else {
|
} else {
|
||||||
output.SetAPIMode(true)
|
output.SetAPIMode(true)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user