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
|
||||
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
|
||||
╭────────────────────────────────────────────────────────────╮
|
||||
│ _____________ │
|
||||
@@ -57,7 +62,7 @@ sif: check_go_version
|
||||
@echo "📁 Current directory: $$(pwd)"
|
||||
@echo "🔧 Go flags: $(GOFLAGS)"
|
||||
@echo "📦 Building package: ./cmd/sif"
|
||||
$(GO) build -v $(GOFLAGS) ./cmd/sif
|
||||
$(GO) build -v $(GOFLAGS) -ldflags "$(GO_LDFLAGS)" ./cmd/sif
|
||||
@echo "📊 Build info:"
|
||||
@$(GO) version -m sif
|
||||
@echo "✅ sif built successfully! 🚀"
|
||||
|
||||
@@ -131,6 +131,20 @@ makepkg -si
|
||||
|
||||
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
|
||||
|
||||
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/internal/config"
|
||||
"github.com/dropalldatabases/sif/internal/patchnotes"
|
||||
ver "github.com/dropalldatabases/sif/internal/version"
|
||||
|
||||
// Register framework 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"
|
||||
|
||||
func main() {
|
||||
version = ver.Resolve(version)
|
||||
sif.Version = version
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
switch os.Args[1] {
|
||||
case "patchnote", "patchnotes", "-pn", "--patchnotes":
|
||||
|
||||
@@ -259,6 +259,28 @@ enable api mode for json output:
|
||||
|
||||
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
|
||||
|
||||
### quick recon
|
||||
|
||||
@@ -98,7 +98,9 @@ func Print(tag string) {
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
// Version is set by main to the resolved build version and shown on the banner.
|
||||
var Version = "dev"
|
||||
|
||||
type UrlResult struct {
|
||||
Url string `json:"url"`
|
||||
Results []ModuleResult
|
||||
@@ -76,7 +79,11 @@ func New(settings *config.Settings) (*App, error) {
|
||||
|
||||
if !settings.ApiMode {
|
||||
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 {
|
||||
output.SetAPIMode(true)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user