mirror of
https://github.com/lunchcat/sif.git
synced 2026-07-28 14:37:01 -07:00
feat(modules): embed built-in modules in the binary (#316)
modules loaded only from an exe-adjacent modules/ dir or ~/.config, so a bare `go install`ed binary shipped zero modules. embed the modules/ tree and fall back to it when no filesystem builtin dir is found. the on-disk builtin dir and the user-override dir still take precedence, so a dev tree and a release that ships the folder alongside the binary keep working unchanged. the embed directive lives at the repo root because go:embed cannot reach a parent directory, and it registers the filesystem with the loader through a package hook to avoid an import cycle back into the root package.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
: :
|
||||
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
||||
: ▄█ █ █▀ · BSD 3-Clause License :
|
||||
: :
|
||||
: (c) 2022-2026 vmfunc, xyzeva, :
|
||||
: lunchcat alumni & contributors :
|
||||
: :
|
||||
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
||||
*/
|
||||
|
||||
package sif
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
"github.com/vmfunc/sif/internal/modules"
|
||||
)
|
||||
|
||||
// builtinModules embeds the module tree into the binary. this file lives at the
|
||||
// repo root because go:embed cannot reference a parent directory, and modules/
|
||||
// sits above internal/modules where the loader lives.
|
||||
//
|
||||
//go:embed modules
|
||||
var builtinModules embed.FS
|
||||
|
||||
// register the embedded modules with the loader once, rooted at the modules
|
||||
// directory so paths match the on-disk layout. the loader only uses this as a
|
||||
// fallback when no filesystem modules/ dir is present.
|
||||
func init() {
|
||||
sub, err := fs.Sub(builtinModules, "modules")
|
||||
if err != nil {
|
||||
log.Debugf("embedded modules unavailable: %v", err)
|
||||
return
|
||||
}
|
||||
modules.SetBuiltinFS(sub)
|
||||
}
|
||||
@@ -14,6 +14,7 @@ package modules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -22,10 +23,21 @@ import (
|
||||
"github.com/vmfunc/sif/internal/output"
|
||||
)
|
||||
|
||||
// builtinFS holds the modules embedded into the binary. it's set once, from the
|
||||
// repo-root package that can actually run the go:embed directive (go:embed can't
|
||||
// reach a parent directory, and modules/ sits above this package). it stays nil
|
||||
// in builds and tests that don't import that package, so the loader simply falls
|
||||
// back to the filesystem as before.
|
||||
var builtinFS fs.FS
|
||||
|
||||
// SetBuiltinFS registers the embedded module filesystem. see builtinFS.
|
||||
func SetBuiltinFS(fsys fs.FS) { builtinFS = fsys }
|
||||
|
||||
// Loader handles module discovery and loading.
|
||||
type Loader struct {
|
||||
builtinDir string
|
||||
userDir string
|
||||
embedded fs.FS
|
||||
loaded int
|
||||
}
|
||||
|
||||
@@ -62,17 +74,28 @@ func NewLoader() (*Loader, error) {
|
||||
return &Loader{
|
||||
builtinDir: builtinDir,
|
||||
userDir: userDir,
|
||||
embedded: builtinFS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoadAll discovers and loads all modules from both built-in
|
||||
// and user directories.
|
||||
func (l *Loader) LoadAll() error {
|
||||
// Load built-in modules first
|
||||
// Load built-in modules first, preferring an on-disk modules/ dir (dev tree
|
||||
// or a release that ships the folder alongside the binary).
|
||||
before := l.loaded
|
||||
if err := l.loadDir(l.builtinDir, false); err != nil {
|
||||
log.Debugf("No built-in modules found: %v", err)
|
||||
}
|
||||
|
||||
// nothing on disk: fall back to the modules embedded in the binary so a bare
|
||||
// `go install`ed sif still ships its built-in modules.
|
||||
if l.loaded == before && l.embedded != nil {
|
||||
if err := l.loadFS(l.embedded); err != nil {
|
||||
log.Debugf("No embedded modules loaded: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load user modules (can override built-in)
|
||||
if err := l.loadDir(l.userDir, true); err != nil {
|
||||
// User dir might not exist, that's OK
|
||||
@@ -118,6 +141,36 @@ func (l *Loader) loadDir(dir string, userDefined bool) error {
|
||||
})
|
||||
}
|
||||
|
||||
// loadFS loads yaml modules from an embedded filesystem. only yaml is embedded
|
||||
// (the .go script path is a filesystem-only dev affordance), so this walks for
|
||||
// yaml files and parses them from bytes.
|
||||
func (l *Loader) loadFS(fsys fs.FS) error {
|
||||
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
switch filepath.Ext(path) {
|
||||
case ".yaml", ".yml":
|
||||
data, rerr := fs.ReadFile(fsys, path)
|
||||
if rerr != nil {
|
||||
log.Warnf("Failed to read embedded module %s: %v", path, rerr)
|
||||
return nil
|
||||
}
|
||||
def, perr := ParseYAMLModuleBytes(data)
|
||||
if perr != nil {
|
||||
log.Warnf("Failed to load embedded module %s: %v", path, perr)
|
||||
return nil
|
||||
}
|
||||
Register(newYAMLModuleWrapper(def, path))
|
||||
l.loaded++
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// loadYAML loads a YAML module definition.
|
||||
func (l *Loader) loadYAML(path string) error {
|
||||
def, err := ParseYAMLModule(path)
|
||||
|
||||
@@ -90,7 +90,12 @@ func ParseYAMLModule(path string) (*YAMLModule, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read module file: %w", err)
|
||||
}
|
||||
return ParseYAMLModuleBytes(data)
|
||||
}
|
||||
|
||||
// ParseYAMLModuleBytes parses and validates a module definition from raw bytes,
|
||||
// so the loader can read modules from an embedded fs.FS as well as from disk.
|
||||
func ParseYAMLModuleBytes(data []byte) (*YAMLModule, error) {
|
||||
var ym YAMLModule
|
||||
if err := yaml.Unmarshal(data, &ym); err != nil {
|
||||
return nil, fmt.Errorf("parse yaml: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user