2024-06-09 17:27:42 +00:00
package cli
import (
2024-07-10 11:18:32 +00:00
"encoding/json"
"errors"
2024-06-09 17:27:42 +00:00
"fmt"
2025-07-14 06:21:29 +00:00
"os"
"path/filepath"
"strings"
2024-06-09 17:27:42 +00:00
2025-07-14 06:21:29 +00:00
"github.com/mholt/archiver/v3"
2025-12-21 18:33:13 +00:00
"github.com/mudler/xlog"
2024-06-09 17:27:42 +00:00
2025-05-02 15:40:26 +00:00
gguf "github.com/gpustack/gguf-parser-go"
2024-06-23 08:24:36 +00:00
cliContext "github.com/mudler/LocalAI/core/cli/context"
2024-07-10 11:18:32 +00:00
"github.com/mudler/LocalAI/core/config"
"github.com/mudler/LocalAI/core/gallery"
"github.com/mudler/LocalAI/pkg/downloader"
2025-07-14 06:21:29 +00:00
"github.com/mudler/LocalAI/pkg/oci"
2025-08-14 17:38:26 +00:00
"github.com/mudler/LocalAI/pkg/system"
2024-06-09 17:27:42 +00:00
)
type UtilCMD struct {
2024-10-01 18:55:46 +00:00
GGUFInfo GGUFInfoCMD ` cmd:"" name:"gguf-info" help:"Get information about a GGUF file" `
2025-07-14 06:21:29 +00:00
CreateOCIImage CreateOCIImageCMD ` cmd:"" name:"create-oci-image" help:"Create an OCI image from a file or a directory" `
2024-10-01 18:55:46 +00:00
HFScan HFScanCMD ` cmd:"" name:"hf-scan" help:"Checks installed models for known security issues. WARNING: this is a best-effort feature and may not catch everything!" `
UsecaseHeuristic UsecaseHeuristicCMD ` cmd:"" name:"usecase-heuristic" help:"Checks a specific model config and prints what usecase LocalAI will offer for it." `
2024-06-09 17:27:42 +00:00
}
type GGUFInfoCMD struct {
2024-06-10 20:58:04 +00:00
Args [ ] string ` arg:"" optional:"" name:"args" help:"Arguments to pass to the utility command" `
Header bool ` optional:"" default:"false" name:"header" help:"Show header information" `
2024-06-09 17:27:42 +00:00
}
2024-07-10 11:18:32 +00:00
type HFScanCMD struct {
ModelsPath string ` env:"LOCALAI_MODELS_PATH,MODELS_PATH" type:"path" default:"$ { basepath}/models" help:"Path containing models used for inferencing" group:"storage" `
Galleries string ` env:"LOCALAI_GALLERIES,GALLERIES" help:"JSON list of galleries" group:"models" default:"$ { galleries}" `
ToScan [ ] string ` arg:"" `
}
2024-10-01 18:55:46 +00:00
type UsecaseHeuristicCMD struct {
ConfigName string ` name:"The config file to check" `
ModelsPath string ` env:"LOCALAI_MODELS_PATH,MODELS_PATH" type:"path" default:"$ { basepath}/models" help:"Path containing models used for inferencing" group:"storage" `
}
2025-07-14 06:21:29 +00:00
type CreateOCIImageCMD struct {
Input [ ] string ` arg:"" help:"Input file or directory to create an OCI image from" `
Output string ` default:"image.tar" help:"Output OCI image name" `
ImageName string ` default:"localai" help:"Image name" `
Platform string ` default:"linux/amd64" help:"Platform of the image" `
}
func ( u * CreateOCIImageCMD ) Run ( ctx * cliContext . Context ) error {
2025-12-21 18:33:13 +00:00
xlog . Info ( "Creating OCI image from input" )
2025-07-14 06:21:29 +00:00
dir , err := os . MkdirTemp ( "" , "localai" )
if err != nil {
return err
}
defer os . RemoveAll ( dir )
err = archiver . Archive ( u . Input , filepath . Join ( dir , "archive.tar" ) )
if err != nil {
return err
}
2025-12-21 18:33:13 +00:00
xlog . Info ( "Creating OCI image" , "output" , u . Output , "input" , u . Input )
2025-07-14 06:21:29 +00:00
platform := strings . Split ( u . Platform , "/" )
if len ( platform ) != 2 {
return fmt . Errorf ( "invalid platform: %s" , u . Platform )
}
return oci . CreateTar ( filepath . Join ( dir , "archive.tar" ) , u . Output , u . ImageName , platform [ 1 ] , platform [ 0 ] )
}
2024-06-09 17:27:42 +00:00
func ( u * GGUFInfoCMD ) Run ( ctx * cliContext . Context ) error {
2025-07-24 13:03:41 +00:00
if len ( u . Args ) == 0 {
2024-06-09 17:27:42 +00:00
return fmt . Errorf ( "no GGUF file provided" )
}
// We try to guess only if we don't have a template defined already
f , err := gguf . ParseGGUFFile ( u . Args [ 0 ] )
if err != nil {
// Only valid for gguf files
2025-12-21 18:33:13 +00:00
xlog . Error ( "guessDefaultsFromFile: not a GGUF file" )
2024-06-09 17:27:42 +00:00
return err
}
2025-12-21 18:33:13 +00:00
xlog . Info ( "GGUF file loaded" , "file" , u . Args [ 0 ] , "eosTokenID" , f . Tokenizer ( ) . EOSTokenID , "bosTokenID" , f . Tokenizer ( ) . BOSTokenID , "modelName" , f . Metadata ( ) . Name , "architecture" , f . Architecture ( ) . Architecture )
2024-06-09 17:27:42 +00:00
2025-12-21 18:33:13 +00:00
xlog . Info ( "Tokenizer" , "tokenizer" , fmt . Sprintf ( "%+v" , f . Tokenizer ( ) ) )
xlog . Info ( "Architecture" , "architecture" , fmt . Sprintf ( "%+v" , f . Architecture ( ) ) )
2024-06-10 20:58:04 +00:00
v , exists := f . Header . MetadataKV . Get ( "tokenizer.chat_template" )
if exists {
2025-12-21 18:33:13 +00:00
xlog . Info ( "chat_template" , "template" , v . ValueString ( ) )
2024-06-10 20:58:04 +00:00
}
if u . Header {
for _ , metadata := range f . Header . MetadataKV {
2025-12-21 18:33:13 +00:00
xlog . Info ( "metadata" , "key" , metadata . Key , "value" , metadata . Value )
2024-06-10 20:58:04 +00:00
}
// log.Info().Any("header", fmt.Sprintf("%+v", f.Header)).Msg("Header")
}
2024-06-09 17:27:42 +00:00
return nil
}
2024-07-10 11:18:32 +00:00
func ( hfscmd * HFScanCMD ) Run ( ctx * cliContext . Context ) error {
2025-08-14 17:38:26 +00:00
systemState , err := system . GetSystemState (
system . WithModelPath ( hfscmd . ModelsPath ) ,
)
if err != nil {
return err
}
2025-12-21 18:33:13 +00:00
xlog . Info ( "LocalAI Security Scanner - This is BEST EFFORT functionality! Currently limited to huggingface models!" )
2024-07-10 11:18:32 +00:00
if len ( hfscmd . ToScan ) == 0 {
2025-12-21 18:33:13 +00:00
xlog . Info ( "Checking all installed models against galleries" )
2024-07-10 11:18:32 +00:00
var galleries [ ] config . Gallery
if err := json . Unmarshal ( [ ] byte ( hfscmd . Galleries ) , & galleries ) ; err != nil {
2025-12-21 18:33:13 +00:00
xlog . Error ( "unable to load galleries" , "error" , err )
2024-07-10 11:18:32 +00:00
}
2025-08-14 17:38:26 +00:00
err := gallery . SafetyScanGalleryModels ( galleries , systemState )
2024-07-10 11:18:32 +00:00
if err == nil {
2025-12-21 18:33:13 +00:00
xlog . Info ( "No security warnings were detected for your installed models. Please note that this is a BEST EFFORT tool, and all issues may not be detected." )
2024-07-10 11:18:32 +00:00
} else {
2025-12-21 18:33:13 +00:00
xlog . Error ( "! WARNING ! A known-vulnerable model is installed!" , "error" , err )
2024-07-10 11:18:32 +00:00
}
return err
} else {
var errs error = nil
for _ , uri := range hfscmd . ToScan {
2025-12-21 18:33:13 +00:00
xlog . Info ( "scanning specific uri" , "uri" , uri )
2024-08-02 18:06:25 +00:00
scanResults , err := downloader . HuggingFaceScan ( downloader . URI ( uri ) )
if err != nil && errors . Is ( err , downloader . ErrUnsafeFilesFound ) {
2025-12-21 18:33:13 +00:00
xlog . Error ( "! WARNING ! A known-vulnerable model is included in this repo!" , "error" , err , "clamAV" , scanResults . ClamAVInfectedFiles , "pickles" , scanResults . DangerousPickles )
2024-07-10 11:18:32 +00:00
errs = errors . Join ( errs , err )
}
}
if errs != nil {
return errs
}
2025-12-21 18:33:13 +00:00
xlog . Info ( "No security warnings were detected for your installed models. Please note that this is a BEST EFFORT tool, and all issues may not be detected." )
2024-07-10 11:18:32 +00:00
return nil
}
}
2024-10-01 18:55:46 +00:00
func ( uhcmd * UsecaseHeuristicCMD ) Run ( ctx * cliContext . Context ) error {
if len ( uhcmd . ConfigName ) == 0 {
2025-12-21 18:33:13 +00:00
xlog . Error ( "ConfigName is a required parameter" )
2024-10-01 18:55:46 +00:00
return fmt . Errorf ( "config name is a required parameter" )
}
if len ( uhcmd . ModelsPath ) == 0 {
2025-12-21 18:33:13 +00:00
xlog . Error ( "ModelsPath is a required parameter" )
2024-10-01 18:55:46 +00:00
return fmt . Errorf ( "model path is a required parameter" )
}
2025-08-14 17:38:26 +00:00
bcl := config . NewModelConfigLoader ( uhcmd . ModelsPath )
err := bcl . ReadModelConfig ( uhcmd . ConfigName )
2024-10-01 18:55:46 +00:00
if err != nil {
2025-12-21 18:33:13 +00:00
xlog . Error ( "error while loading backend" , "error" , err , "ConfigName" , uhcmd . ConfigName )
2024-10-01 18:55:46 +00:00
return err
}
2025-08-14 17:38:26 +00:00
bc , exists := bcl . GetModelConfig ( uhcmd . ConfigName )
2024-10-01 18:55:46 +00:00
if ! exists {
2025-12-21 18:33:13 +00:00
xlog . Error ( "ConfigName not found" , "ConfigName" , uhcmd . ConfigName )
2024-10-01 18:55:46 +00:00
}
2025-08-14 17:38:26 +00:00
for name , uc := range config . GetAllModelConfigUsecases ( ) {
2024-10-01 18:55:46 +00:00
if bc . HasUsecases ( uc ) {
2025-12-21 18:33:13 +00:00
xlog . Info ( "Usecase" , "usecase" , name )
2024-10-01 18:55:46 +00:00
}
}
2025-12-21 18:33:13 +00:00
xlog . Info ( "---" )
2024-10-01 18:55:46 +00:00
return nil
}