feat: add shell opt
License: MIT Signed-off-by: Henrique Dias <hacdias@gmail.com>
This commit is contained in:
parent
06ccf4528c
commit
88effa0889
@ -38,17 +38,6 @@ func (c ConfigStore) SaveSettings(s *types.Settings) error {
|
||||
return c.Save("settings", s)
|
||||
}
|
||||
|
||||
// GetRunner is an helper method to get a runner object.
|
||||
func (c ConfigStore) GetRunner() (*types.Runner, error) {
|
||||
runner := &types.Runner{}
|
||||
return runner, c.Get("runner", runner)
|
||||
}
|
||||
|
||||
// SaveRunner is an helper method to set the runner object
|
||||
func (c ConfigStore) SaveRunner(r *types.Runner) error {
|
||||
return c.Save("runner", r)
|
||||
}
|
||||
|
||||
// GetAuther is an helper method to get an auther object.
|
||||
func (c ConfigStore) GetAuther(t types.AuthMethod) (types.Auther, error) {
|
||||
if t == auth.MethodJSONAuth {
|
||||
|
||||
@ -21,15 +21,15 @@ var cmdsAddCmd = &cobra.Command{
|
||||
db := getDB()
|
||||
defer db.Close()
|
||||
st := getStore(db)
|
||||
r, err := st.Config.GetRunner()
|
||||
s, err := st.Config.GetSettings()
|
||||
checkErr(err)
|
||||
|
||||
evt := mustGetString(cmd, "event")
|
||||
command := mustGetString(cmd, "command")
|
||||
|
||||
r.Commands[evt] = append(r.Commands[evt], command)
|
||||
err = st.Config.SaveRunner(r)
|
||||
s.Commands[evt] = append(s.Commands[evt], command)
|
||||
err = st.Config.SaveSettings(s)
|
||||
checkErr(err)
|
||||
printEvents(r.Commands)
|
||||
printEvents(s.Commands)
|
||||
},
|
||||
}
|
||||
|
||||
@ -18,16 +18,16 @@ var cmdsLsCmd = &cobra.Command{
|
||||
db := getDB()
|
||||
defer db.Close()
|
||||
st := getStore(db)
|
||||
r, err := st.Config.GetRunner()
|
||||
s, err := st.Config.GetSettings()
|
||||
checkErr(err)
|
||||
evt := mustGetString(cmd, "event")
|
||||
|
||||
if evt == "" {
|
||||
printEvents(r.Commands)
|
||||
printEvents(s.Commands)
|
||||
} else {
|
||||
show := map[string][]string{}
|
||||
show["before_"+evt] = r.Commands["before_"+evt]
|
||||
show["after_"+evt] = r.Commands["after_"+evt]
|
||||
show["before_"+evt] = s.Commands["before_"+evt]
|
||||
show["after_"+evt] = s.Commands["after_"+evt]
|
||||
printEvents(show)
|
||||
}
|
||||
},
|
||||
|
||||
@ -21,16 +21,16 @@ var cmdsRmCmd = &cobra.Command{
|
||||
db := getDB()
|
||||
defer db.Close()
|
||||
st := getStore(db)
|
||||
r, err := st.Config.GetRunner()
|
||||
s, err := st.Config.GetSettings()
|
||||
checkErr(err)
|
||||
|
||||
evt := mustGetString(cmd, "event")
|
||||
i, err := cmd.Flags().GetUint("index")
|
||||
checkErr(err)
|
||||
|
||||
r.Commands[evt] = append(r.Commands[evt][:i], r.Commands[evt][i+1:]...)
|
||||
err = st.Config.SaveRunner(r)
|
||||
s.Commands[evt] = append(s.Commands[evt][:i], s.Commands[evt][i+1:]...)
|
||||
err = st.Config.SaveSettings(s)
|
||||
checkErr(err)
|
||||
printEvents(r.Commands)
|
||||
printEvents(s.Commands)
|
||||
},
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
}
|
||||
|
||||
@ -53,7 +53,7 @@ override the options.`,
|
||||
checkErr(err)
|
||||
defer db.Close()
|
||||
|
||||
saveConfig(db, settings, &types.Runner{}, auther)
|
||||
saveConfig(db, settings, auther)
|
||||
|
||||
fmt.Printf(`
|
||||
Congratulations! You've set up your database to use with File Browser.
|
||||
@ -64,12 +64,10 @@ need to call the main command to boot up the server.
|
||||
},
|
||||
}
|
||||
|
||||
func saveConfig(db *storm.DB, s *types.Settings, r *types.Runner, a types.Auther) {
|
||||
func saveConfig(db *storm.DB, s *types.Settings, a types.Auther) {
|
||||
st := getStore(db)
|
||||
err := st.Config.SaveSettings(s)
|
||||
checkErr(err)
|
||||
err = st.Config.SaveRunner(r)
|
||||
checkErr(err)
|
||||
err = st.Config.SaveAuther(a)
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
@ -65,8 +65,6 @@ listening on loalhost on a random port. Use the flags to change it.`,
|
||||
checkErr(err)
|
||||
env.Auther, err = env.Store.Config.GetAuther(env.Settings.AuthMethod)
|
||||
checkErr(err)
|
||||
env.Runner, err = env.Store.Config.GetRunner()
|
||||
checkErr(err)
|
||||
|
||||
startServer(cmd, env)
|
||||
},
|
||||
@ -133,7 +131,7 @@ func quickSetup(cmd *cobra.Command) {
|
||||
checkErr(err)
|
||||
defer db.Close()
|
||||
|
||||
saveConfig(db, settings, &types.Runner{}, &auth.JSONAuth{})
|
||||
saveConfig(db, settings, &auth.JSONAuth{})
|
||||
|
||||
st := getStore(db)
|
||||
err = st.Users.Save(user)
|
||||
|
||||
2
frontend
2
frontend
@ -1 +1 @@
|
||||
Subproject commit eb33671e572daddf57a3bbdf47e3f09865e27837
|
||||
Subproject commit 76ee2aa4d628d03e5d444a98a41b7d59bdba9b7e
|
||||
@ -27,7 +27,6 @@ type modifyRequest struct {
|
||||
// Env contains the required info for FB to run.
|
||||
type Env struct {
|
||||
Auther types.Auther
|
||||
Runner *types.Runner
|
||||
Settings *types.Settings
|
||||
Store *types.Store
|
||||
mux sync.Mutex // settings mutex for Auther, Runner and Settings changes.
|
||||
|
||||
@ -105,7 +105,7 @@ func (e *Env) resourceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err := e.Runner.Run(func() error {
|
||||
err := e.Settings.Run(func() error {
|
||||
return user.Fs.RemoveAll(path)
|
||||
}, "delete", path, "", user)
|
||||
|
||||
@ -156,7 +156,7 @@ func (e *Env) resourcePostPutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
err := e.Runner.Run(func() error {
|
||||
err := e.Settings.Run(func() error {
|
||||
file, err := user.Fs.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -222,7 +222,7 @@ func (e *Env) resourcePatchHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
err = e.Runner.Run(func() error {
|
||||
err = e.Settings.Run(func() error {
|
||||
if action == "copy" {
|
||||
return fileutils.Copy(user.Fs, src, dst)
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ type settingsData struct {
|
||||
Defaults types.UserDefaults `json:"defaults"`
|
||||
Rules []types.Rule `json:"rules"`
|
||||
Branding types.Branding `json:"branding"`
|
||||
Shell []string `json:"shell"`
|
||||
Commands map[string][]string `json:"commands"`
|
||||
}
|
||||
|
||||
@ -27,7 +28,8 @@ func (e *Env) settingsGetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Defaults: e.Settings.Defaults,
|
||||
Rules: e.Settings.Rules,
|
||||
Branding: e.Settings.Branding,
|
||||
Commands: e.Runner.Commands,
|
||||
Shell: e.Settings.Shell,
|
||||
Commands: e.Settings.Commands,
|
||||
}
|
||||
|
||||
renderJSON(w, r, data)
|
||||
@ -49,13 +51,6 @@ func (e *Env) settingsPutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
e.mux.Lock()
|
||||
defer e.mux.Unlock()
|
||||
|
||||
runner := &types.Runner{Commands: req.Commands}
|
||||
err = e.Store.Config.SaveRunner(runner)
|
||||
if err != nil {
|
||||
httpErr(w, r, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
settings := &types.Settings{}
|
||||
err = copier.Copy(settings, e.Settings)
|
||||
if err != nil {
|
||||
@ -67,6 +62,8 @@ func (e *Env) settingsPutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
settings.Defaults = req.Defaults
|
||||
settings.Rules = req.Rules
|
||||
settings.Branding = req.Branding
|
||||
settings.Shell = req.Shell
|
||||
settings.Commands = req.Commands
|
||||
|
||||
err = e.Store.Config.SaveSettings(settings)
|
||||
if err != nil {
|
||||
@ -74,6 +71,5 @@ func (e *Env) settingsPutHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
e.Runner = runner
|
||||
e.Settings = settings
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ func (e *Env) commandsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var command []string
|
||||
var raw string
|
||||
|
||||
for {
|
||||
_, msg, err := conn.ReadMessage()
|
||||
@ -47,13 +47,13 @@ func (e *Env) commandsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
command = strings.Split(string(msg), " ")
|
||||
if len(command) != 0 {
|
||||
raw = strings.TrimSpace(string(msg))
|
||||
if raw != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !user.CanExecute(command[0]) {
|
||||
if !user.CanExecute(strings.Split(raw, " ")[0]) {
|
||||
err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed)
|
||||
if err != nil {
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
@ -62,15 +62,7 @@ func (e *Env) commandsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := exec.LookPath(command[0]); err != nil {
|
||||
err := conn.WriteMessage(websocket.TextMessage, cmdNotImplemented)
|
||||
if err != nil {
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
command, err := e.Settings.ParseCommand(raw)
|
||||
path := strings.TrimPrefix(r.URL.Path, "/api/command")
|
||||
dir := afero.FullBaseFsPath(user.Fs.(*afero.BasePathFs), path)
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
@ -81,6 +73,7 @@ func (e *Env) commandsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
wsErr(conn, r, http.StatusInternalServerError, err)
|
||||
|
||||
139
types/runner.go
139
types/runner.go
@ -19,81 +19,90 @@ var defaultEvents = []string{
|
||||
"delete",
|
||||
}
|
||||
|
||||
// Runner runs certain commands.
|
||||
type Runner struct {
|
||||
Commands map[string][]string `json:"commands"`
|
||||
}
|
||||
|
||||
// Run runs the hooks for the before and after event.
|
||||
func (r Runner) Run(fn func() error, event string, path string, dst string, u *User) error {
|
||||
path = afero.FullBaseFsPath(u.Fs.(*afero.BasePathFs), path)
|
||||
dst = afero.FullBaseFsPath(u.Fs.(*afero.BasePathFs), dst)
|
||||
err := r.do("before_"+event, path, dst, u)
|
||||
func (s *Settings) Run(fn func() error, evt, path, dst string, user *User) error {
|
||||
path = afero.FullBaseFsPath(user.Fs.(*afero.BasePathFs), path)
|
||||
dst = afero.FullBaseFsPath(user.Fs.(*afero.BasePathFs), dst)
|
||||
|
||||
if val, ok := s.Commands["before_"+evt]; ok {
|
||||
for _, command := range val {
|
||||
err := s.exec(command, "before_"+evt, path, dst, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := fn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = fn()
|
||||
if val, ok := s.Commands["after_"+evt]; ok {
|
||||
for _, command := range val {
|
||||
err := s.exec(command, "after_"+evt, path, dst, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.do("after_"+event, path, dst, u)
|
||||
}
|
||||
|
||||
func (r Runner) do(event string, path string, destination string, user *User) error {
|
||||
commands := []string{}
|
||||
|
||||
if val, ok := r.Commands[event]; ok {
|
||||
commands = append(commands, val...)
|
||||
}
|
||||
|
||||
for _, command := range commands {
|
||||
if command == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
args := strings.Split(command, " ")
|
||||
nonblock := false
|
||||
|
||||
if len(args) > 1 && args[len(args)-1] == "&" {
|
||||
nonblock = true
|
||||
args = args[:len(args)-1]
|
||||
}
|
||||
|
||||
command, args, err := caddy.SplitCommandAndArgs(strings.Join(args, " "))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", event))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
|
||||
|
||||
if destination != "" {
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", destination))
|
||||
}
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if nonblock {
|
||||
log.Printf("[INFO] Nonblocking Command:\"%s %s\"", command, strings.Join(args, " "))
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Blocking Command:\"%s %s\"", command, strings.Join(args, " "))
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseCommand parses the command taking in account
|
||||
func (s *Settings) ParseCommand(raw string) ([]string, error) {
|
||||
command := []string{}
|
||||
|
||||
if len(s.Shell) == 0 {
|
||||
cmd, args, err := caddy.SplitCommandAndArgs(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = exec.LookPath(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
command = append(command, cmd)
|
||||
command = append(command, args...)
|
||||
} else {
|
||||
command = append(s.Shell, raw)
|
||||
}
|
||||
|
||||
return command, nil
|
||||
}
|
||||
|
||||
func (s *Settings) exec(raw, evt, path, dst string, user *User) error {
|
||||
blocking := true
|
||||
|
||||
if strings.HasSuffix(raw, "&") {
|
||||
blocking = false
|
||||
raw = strings.TrimSpace(strings.TrimSuffix(raw, "&"))
|
||||
}
|
||||
|
||||
command, err := s.ParseCommand(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command(command[0], command[1:]...)
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("FILE=%s", path))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("SCOPE=%s", user.Scope))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("TRIGGER=%s", evt))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("USERNAME=%s", user.Username))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("DESTINATION=%s", dst))
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
if !blocking {
|
||||
log.Printf("[INFO] Nonblocking Command: \"%s\"", strings.Join(command, " "))
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Blocking Command: \"%s\"", strings.Join(command, " "))
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ type Settings struct {
|
||||
Defaults UserDefaults `json:"defaults"`
|
||||
AuthMethod AuthMethod `json:"authMethod"`
|
||||
Branding Branding `json:"branding"`
|
||||
Commands map[string][]string `json:"commands"`
|
||||
Shell []string `json:"shell"` // TODO add to cli
|
||||
Rules []Rule `json:"rules"` // TODO: use this add to cli
|
||||
}
|
||||
|
||||
|
||||
@ -6,8 +6,6 @@ import "strings"
|
||||
type ConfigStore interface {
|
||||
GetSettings() (*Settings, error)
|
||||
SaveSettings(*Settings) error
|
||||
SaveRunner(*Runner) error
|
||||
GetRunner() (*Runner, error)
|
||||
GetAuther(AuthMethod) (Auther, error)
|
||||
SaveAuther(Auther) error
|
||||
}
|
||||
@ -46,31 +44,25 @@ func (v ConfigVerify) SaveSettings(s *Settings) error {
|
||||
s.Rules = []Rule{}
|
||||
}
|
||||
|
||||
return v.Store.SaveSettings(s)
|
||||
if s.Shell == nil {
|
||||
s.Shell = []string{}
|
||||
}
|
||||
|
||||
// GetRunner wraps a ConfigStore.GetRunner
|
||||
func (v ConfigVerify) GetRunner() (*Runner, error) {
|
||||
return v.Store.GetRunner()
|
||||
}
|
||||
|
||||
// SaveRunner wraps a ConfigStore.SaveRunner
|
||||
func (v ConfigVerify) SaveRunner(r *Runner) error {
|
||||
if r.Commands == nil {
|
||||
r.Commands = map[string][]string{}
|
||||
if s.Commands == nil {
|
||||
s.Commands = map[string][]string{}
|
||||
}
|
||||
|
||||
for _, event := range defaultEvents {
|
||||
if _, ok := r.Commands["before_"+event]; !ok {
|
||||
r.Commands["before_"+event] = []string{}
|
||||
if _, ok := s.Commands["before_"+event]; !ok {
|
||||
s.Commands["before_"+event] = []string{}
|
||||
}
|
||||
|
||||
if _, ok := r.Commands["after_"+event]; !ok {
|
||||
r.Commands["after_"+event] = []string{}
|
||||
if _, ok := s.Commands["after_"+event]; !ok {
|
||||
s.Commands["after_"+event] = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
return v.Store.SaveRunner(r)
|
||||
return v.Store.SaveSettings(s)
|
||||
}
|
||||
|
||||
// GetAuther wraps a ConfigStore.GetAuther
|
||||
|
||||
Loading…
Reference in New Issue
Block a user