mirror of https://github.com/curusarn/resh
parent
e06fe26375
commit
f5349fc45b
@ -1,66 +0,0 @@ |
||||
package cmd |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
"github.com/curusarn/resh/cmd/control/status" |
||||
"github.com/spf13/cobra" |
||||
) |
||||
|
||||
var debugCmd = &cobra.Command{ |
||||
Use: "debug", |
||||
Short: "debug utils for resh", |
||||
Long: "Reloads resh rc files. Shows logs and output from last runs of resh", |
||||
} |
||||
|
||||
var debugReloadCmd = &cobra.Command{ |
||||
Use: "reload", |
||||
Short: "reload resh rc files", |
||||
Long: "Reload resh rc files", |
||||
Run: func(cmd *cobra.Command, args []string) { |
||||
exitCode = status.ReloadRcFiles |
||||
}, |
||||
} |
||||
|
||||
var debugInspectCmd = &cobra.Command{ |
||||
Use: "inspect", |
||||
Short: "inspect session history", |
||||
Run: func(cmd *cobra.Command, args []string) { |
||||
exitCode = status.InspectSessionHistory |
||||
}, |
||||
} |
||||
|
||||
var debugOutputCmd = &cobra.Command{ |
||||
Use: "output", |
||||
Short: "shows output from last runs of resh", |
||||
Long: "Shows output from last runs of resh", |
||||
Run: func(cmd *cobra.Command, args []string) { |
||||
files := []string{ |
||||
"daemon_last_run_out.txt", |
||||
"collect_last_run_out.txt", |
||||
"postcollect_last_run_out.txt", |
||||
"session_init_last_run_out.txt", |
||||
"cli_last_run_out.txt", |
||||
} |
||||
dir := os.Getenv("__RESH_XDG_CACHE_HOME") |
||||
for _, fpath := range files { |
||||
fpath := filepath.Join(dir, fpath) |
||||
debugReadFile(fpath) |
||||
} |
||||
exitCode = status.Success |
||||
}, |
||||
} |
||||
|
||||
func debugReadFile(path string) { |
||||
fmt.Println("============================================================") |
||||
fmt.Println(" filepath:", path) |
||||
fmt.Println("============================================================") |
||||
dat, err := ioutil.ReadFile(path) |
||||
if err != nil { |
||||
fmt.Println("ERROR while reading file:", err) |
||||
} |
||||
fmt.Println(string(dat)) |
||||
} |
||||
@ -1,25 +0,0 @@ |
||||
package status |
||||
|
||||
// Code - exit code of the resh-control command
|
||||
type Code int |
||||
|
||||
const ( |
||||
// Success exit code
|
||||
Success Code = 0 |
||||
// Fail exit code
|
||||
Fail = 1 |
||||
// EnableResh exit code - tells reshctl() wrapper to enable resh
|
||||
// EnableResh = 30
|
||||
|
||||
// EnableControlRBinding exit code - tells reshctl() wrapper to enable control R binding
|
||||
EnableControlRBinding = 32 |
||||
|
||||
// DisableControlRBinding exit code - tells reshctl() wrapper to disable control R binding
|
||||
DisableControlRBinding = 42 |
||||
// ReloadRcFiles exit code - tells reshctl() wrapper to reload shellrc resh file
|
||||
ReloadRcFiles = 50 |
||||
// InspectSessionHistory exit code - tells reshctl() wrapper to take current sessionID and send /inspect request to daemon
|
||||
InspectSessionHistory = 51 |
||||
// ReshStatus exit code - tells reshctl() wrapper to show RESH status (aka systemctl status)
|
||||
ReshStatus = 52 |
||||
) |
||||
@ -1,28 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"io/ioutil" |
||||
"log" |
||||
"os/exec" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
func killDaemon(pidfile string) error { |
||||
dat, err := ioutil.ReadFile(pidfile) |
||||
if err != nil { |
||||
log.Println("Reading pid file failed", err) |
||||
} |
||||
log.Print(string(dat)) |
||||
pid, err := strconv.Atoi(strings.TrimSuffix(string(dat), "\n")) |
||||
if err != nil { |
||||
log.Fatal("Pidfile contents are malformed", err) |
||||
} |
||||
cmd := exec.Command("kill", "-s", "sigint", strconv.Itoa(pid)) |
||||
err = cmd.Run() |
||||
if err != nil { |
||||
log.Printf("Command finished with error: %v", err) |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
@ -1,87 +1,141 @@ |
||||
package main |
||||
|
||||
import ( |
||||
//"flag"
|
||||
|
||||
"fmt" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"os/user" |
||||
"os/exec" |
||||
"path/filepath" |
||||
"strconv" |
||||
"strings" |
||||
|
||||
"github.com/BurntSushi/toml" |
||||
"github.com/curusarn/resh/pkg/cfg" |
||||
"github.com/curusarn/resh/internal/cfg" |
||||
"github.com/curusarn/resh/internal/httpclient" |
||||
"github.com/curusarn/resh/internal/logger" |
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
// version from git set during build
|
||||
// info passed during build
|
||||
var version string |
||||
|
||||
// commit from git set during build
|
||||
var commit string |
||||
|
||||
// Debug switch
|
||||
var Debug = false |
||||
var developement bool |
||||
|
||||
func main() { |
||||
log.Println("Daemon starting... \n" + |
||||
"version: " + version + |
||||
" commit: " + commit) |
||||
usr, _ := user.Current() |
||||
dir := usr.HomeDir |
||||
pidfilePath := filepath.Join(dir, ".resh/resh.pid") |
||||
configPath := filepath.Join(dir, ".config/resh.toml") |
||||
reshHistoryPath := filepath.Join(dir, ".resh_history.json") |
||||
bashHistoryPath := filepath.Join(dir, ".bash_history") |
||||
zshHistoryPath := filepath.Join(dir, ".zsh_history") |
||||
logPath := filepath.Join(dir, ".resh/daemon.log") |
||||
|
||||
f, err := os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) |
||||
if err != nil { |
||||
log.Fatalf("Error opening file: %v\n", err) |
||||
config, errCfg := cfg.New() |
||||
logger, _ := logger.New("daemon", config.LogLevel, developement) |
||||
defer logger.Sync() // flushes buffer, if any
|
||||
if errCfg != nil { |
||||
logger.Error("Error while getting configuration", zap.Error(errCfg)) |
||||
} |
||||
defer f.Close() |
||||
sugar := logger.Sugar() |
||||
d := daemon{sugar: sugar} |
||||
sugar.Infow("Deamon starting ...", |
||||
"version", version, |
||||
"commit", commit, |
||||
) |
||||
|
||||
log.SetOutput(f) |
||||
log.SetPrefix(strconv.Itoa(os.Getpid()) + " | ") |
||||
// xdgCacheHome := d.getEnvOrPanic("__RESH_XDG_CACHE_HOME")
|
||||
// xdgDataHome := d.getEnvOrPanic("__RESH_XDG_DATA_HOME")
|
||||
|
||||
var config cfg.Config |
||||
if _, err := toml.DecodeFile(configPath, &config); err != nil { |
||||
log.Printf("Error reading config: %v\n", err) |
||||
return |
||||
} |
||||
if config.Debug { |
||||
Debug = true |
||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds) |
||||
// TODO: rethink PID file and logs location
|
||||
homeDir, err := os.UserHomeDir() |
||||
if err != nil { |
||||
sugar.Fatalw("Could not get user home dir", zap.Error(err)) |
||||
} |
||||
PIDFile := filepath.Join(homeDir, ".resh/resh.pid") |
||||
reshHistoryPath := filepath.Join(homeDir, ".resh_history.json") |
||||
bashHistoryPath := filepath.Join(homeDir, ".bash_history") |
||||
zshHistoryPath := filepath.Join(homeDir, ".zsh_history") |
||||
|
||||
sugar = sugar.With(zap.Int("daemonPID", os.Getpid())) |
||||
|
||||
res, err := isDaemonRunning(config.Port) |
||||
res, err := d.isDaemonRunning(config.Port) |
||||
if err != nil { |
||||
log.Printf("Error while checking if the daemon is runnnig"+ |
||||
" - it's probably not running: %v\n", err) |
||||
sugar.Errorw("Error while checking daemon status - "+ |
||||
"it's probably not running", "error", err) |
||||
} |
||||
if res { |
||||
log.Println("Daemon is already running - exiting!") |
||||
sugar.Errorw("Daemon is already running - exiting!") |
||||
return |
||||
} |
||||
_, err = os.Stat(pidfilePath) |
||||
_, err = os.Stat(PIDFile) |
||||
if err == nil { |
||||
log.Println("Pidfile exists") |
||||
sugar.Warn("Pidfile exists") |
||||
// kill daemon
|
||||
err = killDaemon(pidfilePath) |
||||
err = d.killDaemon(PIDFile) |
||||
if err != nil { |
||||
sugar.Errorw("Could not kill daemon", |
||||
"error", err, |
||||
) |
||||
} |
||||
} |
||||
err = ioutil.WriteFile(PIDFile, []byte(strconv.Itoa(os.Getpid())), 0644) |
||||
if err != nil { |
||||
sugar.Fatalw("Could not create pidfile", |
||||
"error", err, |
||||
"PIDFile", PIDFile, |
||||
) |
||||
} |
||||
server := Server{ |
||||
sugar: sugar, |
||||
config: config, |
||||
reshHistoryPath: reshHistoryPath, |
||||
bashHistoryPath: bashHistoryPath, |
||||
zshHistoryPath: zshHistoryPath, |
||||
} |
||||
server.Run() |
||||
sugar.Infow("Removing PID file ...", |
||||
"PIDFile", PIDFile, |
||||
) |
||||
err = os.Remove(PIDFile) |
||||
if err != nil { |
||||
sugar.Errorw("Could not delete PID file", "error", err) |
||||
} |
||||
sugar.Info("Shutting down ...") |
||||
} |
||||
|
||||
type daemon struct { |
||||
sugar *zap.SugaredLogger |
||||
} |
||||
|
||||
func (d *daemon) getEnvOrPanic(envVar string) string { |
||||
val, found := os.LookupEnv(envVar) |
||||
if !found { |
||||
d.sugar.Fatalw("Required env variable is not set", |
||||
"variableName", envVar, |
||||
) |
||||
} |
||||
return val |
||||
} |
||||
|
||||
func (d *daemon) isDaemonRunning(port int) (bool, error) { |
||||
url := "http://localhost:" + strconv.Itoa(port) + "/status" |
||||
client := httpclient.New() |
||||
resp, err := client.Get(url) |
||||
if err != nil { |
||||
log.Printf("Error while killing daemon: %v\n", err) |
||||
return false, err |
||||
} |
||||
defer resp.Body.Close() |
||||
return true, nil |
||||
} |
||||
|
||||
func (d *daemon) killDaemon(pidfile string) error { |
||||
dat, err := ioutil.ReadFile(pidfile) |
||||
if err != nil { |
||||
d.sugar.Errorw("Reading pid file failed", |
||||
"PIDFile", pidfile, |
||||
"error", err) |
||||
} |
||||
err = ioutil.WriteFile(pidfilePath, []byte(strconv.Itoa(os.Getpid())), 0644) |
||||
d.sugar.Infow("Succesfully read PID file", "contents", string(dat)) |
||||
pid, err := strconv.Atoi(strings.TrimSuffix(string(dat), "\n")) |
||||
if err != nil { |
||||
log.Fatalf("Could not create pidfile: %v\n", err) |
||||
return fmt.Errorf("could not parse PID file contents: %w", err) |
||||
} |
||||
runServer(config, reshHistoryPath, bashHistoryPath, zshHistoryPath) |
||||
log.Println("main: Removing pidfile ...") |
||||
err = os.Remove(pidfilePath) |
||||
d.sugar.Infow("Successfully parsed PID", "PID", pid) |
||||
cmd := exec.Command("kill", "-s", "sigint", strconv.Itoa(pid)) |
||||
err = cmd.Run() |
||||
if err != nil { |
||||
log.Printf("Could not delete pidfile: %v\n", err) |
||||
return fmt.Errorf("kill command finished with error: %w", err) |
||||
} |
||||
log.Println("main: Shutdown - bye") |
||||
return nil |
||||
} |
||||
|
||||
@ -1,523 +0,0 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"crypto/sha256" |
||||
"encoding/binary" |
||||
"encoding/hex" |
||||
"encoding/json" |
||||
"errors" |
||||
"flag" |
||||
"fmt" |
||||
"log" |
||||
"math" |
||||
"net/url" |
||||
"os" |
||||
"os/user" |
||||
"path" |
||||
"path/filepath" |
||||
"strconv" |
||||
"strings" |
||||
"unicode" |
||||
|
||||
"github.com/coreos/go-semver/semver" |
||||
"github.com/curusarn/resh/pkg/records" |
||||
giturls "github.com/whilp/git-urls" |
||||
) |
||||
|
||||
// version from git set during build
|
||||
var version string |
||||
|
||||
// commit from git set during build
|
||||
var commit string |
||||
|
||||
func main() { |
||||
usr, _ := user.Current() |
||||
dir := usr.HomeDir |
||||
historyPath := filepath.Join(dir, ".resh_history.json") |
||||
// outputPath := filepath.Join(dir, "resh_history_sanitized.json")
|
||||
sanitizerDataPath := filepath.Join(dir, ".resh", "sanitizer_data") |
||||
|
||||
showVersion := flag.Bool("version", false, "Show version and exit") |
||||
showRevision := flag.Bool("revision", false, "Show git revision and exit") |
||||
trimHashes := flag.Int("trim-hashes", 12, "Trim hashes to N characters, '0' turns off trimming") |
||||
inputPath := flag.String("input", historyPath, "Input file") |
||||
outputPath := flag.String("output", "", "Output file (default: use stdout)") |
||||
|
||||
flag.Parse() |
||||
|
||||
if *showVersion == true { |
||||
fmt.Println(version) |
||||
os.Exit(0) |
||||
} |
||||
if *showRevision == true { |
||||
fmt.Println(commit) |
||||
os.Exit(0) |
||||
} |
||||
sanitizer := sanitizer{hashLength: *trimHashes} |
||||
err := sanitizer.init(sanitizerDataPath) |
||||
if err != nil { |
||||
log.Fatal("Sanitizer init() error:", err) |
||||
} |
||||
|
||||
inputFile, err := os.Open(*inputPath) |
||||
if err != nil { |
||||
log.Fatal("Open() resh history file error:", err) |
||||
} |
||||
defer inputFile.Close() |
||||
|
||||
var writer *bufio.Writer |
||||
if *outputPath == "" { |
||||
writer = bufio.NewWriter(os.Stdout) |
||||
} else { |
||||
outputFile, err := os.Create(*outputPath) |
||||
if err != nil { |
||||
log.Fatal("Create() output file error:", err) |
||||
} |
||||
defer outputFile.Close() |
||||
writer = bufio.NewWriter(outputFile) |
||||
} |
||||
defer writer.Flush() |
||||
|
||||
scanner := bufio.NewScanner(inputFile) |
||||
for scanner.Scan() { |
||||
record := records.Record{} |
||||
fallbackRecord := records.FallbackRecord{} |
||||
line := scanner.Text() |
||||
err = json.Unmarshal([]byte(line), &record) |
||||
if err != nil { |
||||
err = json.Unmarshal([]byte(line), &fallbackRecord) |
||||
if err != nil { |
||||
log.Println("Line:", line) |
||||
log.Fatal("Decoding error:", err) |
||||
} |
||||
record = records.Convert(&fallbackRecord) |
||||
} |
||||
err = sanitizer.sanitizeRecord(&record) |
||||
if err != nil { |
||||
log.Println("Line:", line) |
||||
log.Fatal("Sanitization error:", err) |
||||
} |
||||
outLine, err := json.Marshal(&record) |
||||
if err != nil { |
||||
log.Println("Line:", line) |
||||
log.Fatal("Encoding error:", err) |
||||
} |
||||
// fmt.Println(string(outLine))
|
||||
n, err := writer.WriteString(string(outLine) + "\n") |
||||
if err != nil { |
||||
log.Fatal(err) |
||||
} |
||||
if n == 0 { |
||||
log.Fatal("Nothing was written", n) |
||||
} |
||||
} |
||||
} |
||||
|
||||
type sanitizer struct { |
||||
hashLength int |
||||
whitelist map[string]bool |
||||
} |
||||
|
||||
func (s *sanitizer) init(dataPath string) error { |
||||
globalData := path.Join(dataPath, "whitelist.txt") |
||||
s.whitelist = loadData(globalData) |
||||
return nil |
||||
} |
||||
|
||||
func loadData(fname string) map[string]bool { |
||||
file, err := os.Open(fname) |
||||
if err != nil { |
||||
log.Fatal("Open() file error:", err) |
||||
} |
||||
defer file.Close() |
||||
|
||||
scanner := bufio.NewScanner(file) |
||||
data := make(map[string]bool) |
||||
for scanner.Scan() { |
||||
line := scanner.Text() |
||||
data[line] = true |
||||
} |
||||
return data |
||||
} |
||||
|
||||
func (s *sanitizer) sanitizeRecord(record *records.Record) error { |
||||
// hash directories of the paths
|
||||
record.Pwd = s.sanitizePath(record.Pwd) |
||||
record.RealPwd = s.sanitizePath(record.RealPwd) |
||||
record.PwdAfter = s.sanitizePath(record.PwdAfter) |
||||
record.RealPwdAfter = s.sanitizePath(record.RealPwdAfter) |
||||
record.GitDir = s.sanitizePath(record.GitDir) |
||||
record.GitDirAfter = s.sanitizePath(record.GitDirAfter) |
||||
record.GitRealDir = s.sanitizePath(record.GitRealDir) |
||||
record.GitRealDirAfter = s.sanitizePath(record.GitRealDirAfter) |
||||
record.Home = s.sanitizePath(record.Home) |
||||
record.ShellEnv = s.sanitizePath(record.ShellEnv) |
||||
|
||||
// hash the most sensitive info, do not tokenize
|
||||
record.Host = s.hashToken(record.Host) |
||||
record.Login = s.hashToken(record.Login) |
||||
record.MachineID = s.hashToken(record.MachineID) |
||||
|
||||
var err error |
||||
// this changes git url a bit but I'm still happy with the result
|
||||
// e.g. "git@github.com:curusarn/resh" becomes "ssh://git@github.com/3385162f14d7/5a7b2909005c"
|
||||
// notice the "ssh://" prefix
|
||||
record.GitOriginRemote, err = s.sanitizeGitURL(record.GitOriginRemote) |
||||
if err != nil { |
||||
log.Println("Error while snitizing GitOriginRemote url", record.GitOriginRemote, ":", err) |
||||
return err |
||||
} |
||||
record.GitOriginRemoteAfter, err = s.sanitizeGitURL(record.GitOriginRemoteAfter) |
||||
if err != nil { |
||||
log.Println("Error while snitizing GitOriginRemoteAfter url", record.GitOriginRemoteAfter, ":", err) |
||||
return err |
||||
} |
||||
|
||||
// sanitization destroys original CmdLine length -> save it
|
||||
record.CmdLength = len(record.CmdLine) |
||||
|
||||
record.CmdLine, err = s.sanitizeCmdLine(record.CmdLine) |
||||
if err != nil { |
||||
log.Fatal("Cmd:", record.CmdLine, "; sanitization error:", err) |
||||
} |
||||
record.RecallLastCmdLine, err = s.sanitizeCmdLine(record.RecallLastCmdLine) |
||||
if err != nil { |
||||
log.Fatal("RecallLastCmdLine:", record.RecallLastCmdLine, "; sanitization error:", err) |
||||
} |
||||
|
||||
if len(record.RecallActionsRaw) > 0 { |
||||
record.RecallActionsRaw, err = s.sanitizeRecallActions(record.RecallActionsRaw, record.ReshVersion) |
||||
if err != nil { |
||||
log.Println("RecallActionsRaw:", record.RecallActionsRaw, "; sanitization error:", err) |
||||
} |
||||
} |
||||
// add a flag to signify that the record has been sanitized
|
||||
record.Sanitized = true |
||||
return nil |
||||
} |
||||
|
||||
func fixSeparator(str string) string { |
||||
if len(str) > 0 && str[0] == ';' { |
||||
return "|||" + str[1:] |
||||
} |
||||
return str |
||||
} |
||||
|
||||
func minIndex(str string, substrs []string) (idx, substrIdx int) { |
||||
minMatch := math.MaxInt32 |
||||
for i, sep := range substrs { |
||||
match := strings.Index(str, sep) |
||||
if match != -1 && match < minMatch { |
||||
minMatch = match |
||||
substrIdx = i |
||||
} |
||||
} |
||||
idx = minMatch |
||||
return |
||||
} |
||||
|
||||
// sanitizes the recall actions by replacing the recall prefix with it's length
|
||||
func (s *sanitizer) sanitizeRecallActions(str string, reshVersion string) (string, error) { |
||||
if len(str) == 0 { |
||||
return "", nil |
||||
} |
||||
var separators []string |
||||
seps := []string{"|||"} |
||||
refVersion, err := semver.NewVersion("2.5.14") |
||||
if err != nil { |
||||
return str, fmt.Errorf("sanitizeRecallActions: semver error: %s", err.Error()) |
||||
} |
||||
if len(reshVersion) == 0 { |
||||
return str, errors.New("sanitizeRecallActions: record.ReshVersion is an empty string") |
||||
} |
||||
if reshVersion == "dev" { |
||||
reshVersion = "0.0.0" |
||||
} |
||||
if reshVersion[0] == 'v' { |
||||
reshVersion = reshVersion[1:] |
||||
} |
||||
recordVersion, err := semver.NewVersion(reshVersion) |
||||
if err != nil { |
||||
return str, fmt.Errorf("sanitizeRecallActions: semver error: %s; version string: %s", err.Error(), reshVersion) |
||||
} |
||||
if recordVersion.LessThan(*refVersion) { |
||||
seps = append(seps, ";") |
||||
} |
||||
|
||||
actions := []string{"arrow_up", "arrow_down", "control_R"} |
||||
for _, sep := range seps { |
||||
for _, action := range actions { |
||||
separators = append(separators, sep+action+":") |
||||
} |
||||
} |
||||
/* |
||||
- find any of {|||,;}{arrow_up,arrow_down,control_R}: in the recallActions (on the lowest index) |
||||
- use found substring to parse out the next prefix |
||||
- sanitize prefix |
||||
- add fixed substring and sanitized prefix to output |
||||
*/ |
||||
doBreak := false |
||||
sanStr := "" |
||||
idx := 0 |
||||
var currSeparator string |
||||
tokenLen, sepIdx := minIndex(str, separators) |
||||
if tokenLen != 0 { |
||||
return str, errors.New("sanitizeReacallActions: unexpected string before first action/separator") |
||||
} |
||||
currSeparator = separators[sepIdx] |
||||
idx += len(currSeparator) |
||||
for !doBreak { |
||||
tokenLen, sepIdx := minIndex(str[idx:], separators) |
||||
if tokenLen > len(str[idx:]) { |
||||
tokenLen = len(str[idx:]) |
||||
doBreak = true |
||||
} |
||||
// token := str[idx : idx+tokenLen]
|
||||
sanStr += fixSeparator(currSeparator) + strconv.Itoa(tokenLen) |
||||
currSeparator = separators[sepIdx] |
||||
idx += tokenLen + len(currSeparator) |
||||
} |
||||
return sanStr, nil |
||||
} |
||||
|
||||
func (s *sanitizer) sanitizeCmdLine(cmdLine string) (string, error) { |
||||
const optionEndingChars = "\"$'\\#[]!><|;{}()*,?~&=`:@^/+%." // all bash control characters, '=', ...
|
||||
const optionAllowedChars = "-_" // characters commonly found inside of options
|
||||
sanCmdLine := "" |
||||
buff := "" |
||||
|
||||
// simple options shouldn't be sanitized
|
||||
// 1) whitespace 2) "-" or "--" 3) letters, digits, "-", "_" 4) ending whitespace or any of "=;)"
|
||||
var optionDetected bool |
||||
|
||||
prevR3 := ' ' |
||||
prevR2 := ' ' |
||||
prevR := ' ' |
||||
for _, r := range cmdLine { |
||||
switch optionDetected { |
||||
case true: |
||||
if unicode.IsSpace(r) || strings.ContainsRune(optionEndingChars, r) { |
||||
// whitespace or option ends the option
|
||||
// => add option unsanitized
|
||||
optionDetected = false |
||||
if len(buff) > 0 { |
||||
sanCmdLine += buff |
||||
buff = "" |
||||
} |
||||
sanCmdLine += string(r) |
||||
} else if unicode.IsLetter(r) == false && unicode.IsDigit(r) == false && |
||||
strings.ContainsRune(optionAllowedChars, r) == false { |
||||
// r is not any of allowed chars for an option: letter, digit, "-" or "_"
|
||||
// => sanitize
|
||||
if len(buff) > 0 { |
||||
sanToken, err := s.sanitizeCmdToken(buff) |
||||
if err != nil { |
||||
log.Println("WARN: got error while sanitizing cmdLine:", cmdLine) |
||||
// return cmdLine, err
|
||||
} |
||||
sanCmdLine += sanToken |
||||
buff = "" |
||||
} |
||||
sanCmdLine += string(r) |
||||
} else { |
||||
buff += string(r) |
||||
} |
||||
case false: |
||||
// split command on all non-letter and non-digit characters
|
||||
if unicode.IsLetter(r) == false && unicode.IsDigit(r) == false { |
||||
// split token
|
||||
if len(buff) > 0 { |
||||
sanToken, err := s.sanitizeCmdToken(buff) |
||||
if err != nil { |
||||
log.Println("WARN: got error while sanitizing cmdLine:", cmdLine) |
||||
// return cmdLine, err
|
||||
} |
||||
sanCmdLine += sanToken |
||||
buff = "" |
||||
} |
||||
sanCmdLine += string(r) |
||||
} else { |
||||
if (unicode.IsSpace(prevR2) && prevR == '-') || |
||||
(unicode.IsSpace(prevR3) && prevR2 == '-' && prevR == '-') { |
||||
optionDetected = true |
||||
} |
||||
buff += string(r) |
||||
} |
||||
} |
||||
prevR3 = prevR2 |
||||
prevR2 = prevR |
||||
prevR = r |
||||
} |
||||
if len(buff) <= 0 { |
||||
// nothing in the buffer => work is done
|
||||
return sanCmdLine, nil |
||||
} |
||||
if optionDetected { |
||||
// option detected => dont sanitize
|
||||
sanCmdLine += buff |
||||
return sanCmdLine, nil |
||||
} |
||||
// sanitize
|
||||
sanToken, err := s.sanitizeCmdToken(buff) |
||||
if err != nil { |
||||
log.Println("WARN: got error while sanitizing cmdLine:", cmdLine) |
||||
// return cmdLine, err
|
||||
} |
||||
sanCmdLine += sanToken |
||||
return sanCmdLine, nil |
||||
} |
||||
|
||||
func (s *sanitizer) sanitizeGitURL(rawURL string) (string, error) { |
||||
if len(rawURL) <= 0 { |
||||
return rawURL, nil |
||||
} |
||||
parsedURL, err := giturls.Parse(rawURL) |
||||
if err != nil { |
||||
return rawURL, err |
||||
} |
||||
return s.sanitizeParsedURL(parsedURL) |
||||
} |
||||
|
||||
func (s *sanitizer) sanitizeURL(rawURL string) (string, error) { |
||||
if len(rawURL) <= 0 { |
||||
return rawURL, nil |
||||
} |
||||
parsedURL, err := url.Parse(rawURL) |
||||
if err != nil { |
||||
return rawURL, err |
||||
} |
||||
return s.sanitizeParsedURL(parsedURL) |
||||
} |
||||
|
||||
func (s *sanitizer) sanitizeParsedURL(parsedURL *url.URL) (string, error) { |
||||
parsedURL.Opaque = s.sanitizeToken(parsedURL.Opaque) |
||||
|
||||
userinfo := parsedURL.User.Username() // only get username => password won't even make it to the sanitized data
|
||||
if len(userinfo) > 0 { |
||||
parsedURL.User = url.User(s.sanitizeToken(userinfo)) |
||||
} else { |
||||
// we need to do this because `gitUrls.Parse()` sets `User` to `url.User("")` instead of `nil`
|
||||
parsedURL.User = nil |
||||
} |
||||
var err error |
||||
parsedURL.Host, err = s.sanitizeTwoPartToken(parsedURL.Host, ":") |
||||
if err != nil { |
||||
return parsedURL.String(), err |
||||
} |
||||
parsedURL.Path = s.sanitizePath(parsedURL.Path) |
||||
// ForceQuery bool
|
||||
parsedURL.RawQuery = s.sanitizeToken(parsedURL.RawQuery) |
||||
parsedURL.Fragment = s.sanitizeToken(parsedURL.Fragment) |
||||
|
||||
return parsedURL.String(), nil |
||||
} |
||||
|
||||
func (s *sanitizer) sanitizePath(path string) string { |
||||
var sanPath string |
||||
for _, token := range strings.Split(path, "/") { |
||||
if s.whitelist[token] != true { |
||||
token = s.hashToken(token) |
||||
} |
||||
sanPath += token + "/" |
||||
} |
||||
if len(sanPath) > 0 { |
||||
sanPath = sanPath[:len(sanPath)-1] |
||||
} |
||||
return sanPath |
||||
} |
||||
|
||||
func (s *sanitizer) sanitizeTwoPartToken(token string, delimeter string) (string, error) { |
||||
tokenParts := strings.Split(token, delimeter) |
||||
if len(tokenParts) <= 1 { |
||||
return s.sanitizeToken(token), nil |
||||
} |
||||
if len(tokenParts) == 2 { |
||||
return s.sanitizeToken(tokenParts[0]) + delimeter + s.sanitizeToken(tokenParts[1]), nil |
||||
} |
||||
return token, errors.New("Token has more than two parts") |
||||
} |
||||
|
||||
func (s *sanitizer) sanitizeCmdToken(token string) (string, error) { |
||||
// there shouldn't be tokens with letters or digits mixed together with symbols
|
||||
if len(token) <= 1 { |
||||
// NOTE: do not sanitize single letter tokens
|
||||
return token, nil |
||||
} |
||||
if s.isInWhitelist(token) == true { |
||||
return token, nil |
||||
} |
||||
|
||||
isLettersOrDigits := true |
||||
// isDigits := true
|
||||
isOtherCharacters := true |
||||
for _, r := range token { |
||||
if unicode.IsDigit(r) == false && unicode.IsLetter(r) == false { |
||||
isLettersOrDigits = false |
||||
// isDigits = false
|
||||
} |
||||
// if unicode.IsDigit(r) == false {
|
||||
// isDigits = false
|
||||
// }
|
||||
if unicode.IsDigit(r) || unicode.IsLetter(r) { |
||||
isOtherCharacters = false |
||||
} |
||||
} |
||||
// NOTE: I decided that I don't want a special sanitization for numbers
|
||||
// if isDigits {
|
||||
// return s.hashNumericToken(token), nil
|
||||
// }
|
||||
if isLettersOrDigits { |
||||
return s.hashToken(token), nil |
||||
} |
||||
if isOtherCharacters { |
||||
return token, nil |
||||
} |
||||
log.Println("WARN: cmd token is made of mix of letters or digits and other characters; token:", token) |
||||
// return token, errors.New("cmd token is made of mix of letters or digits and other characters")
|
||||
return s.hashToken(token), errors.New("cmd token is made of mix of letters or digits and other characters") |
||||
} |
||||
|
||||
func (s *sanitizer) sanitizeToken(token string) string { |
||||
if len(token) <= 1 { |
||||
// NOTE: do not sanitize single letter tokens
|
||||
return token |
||||
} |
||||
if s.isInWhitelist(token) { |
||||
return token |
||||
} |
||||
return s.hashToken(token) |
||||
} |
||||
|
||||
func (s *sanitizer) hashToken(token string) string { |
||||
if len(token) <= 0 { |
||||
return token |
||||
} |
||||
// hash with sha256
|
||||
sum := sha256.Sum256([]byte(token)) |
||||
return s.trimHash(hex.EncodeToString(sum[:])) |
||||
} |
||||
|
||||
func (s *sanitizer) hashNumericToken(token string) string { |
||||
if len(token) <= 0 { |
||||
return token |
||||
} |
||||
sum := sha256.Sum256([]byte(token)) |
||||
sumInt := int(binary.LittleEndian.Uint64(sum[:])) |
||||
if sumInt < 0 { |
||||
return strconv.Itoa(sumInt * -1) |
||||
} |
||||
return s.trimHash(strconv.Itoa(sumInt)) |
||||
} |
||||
|
||||
func (s *sanitizer) trimHash(hash string) string { |
||||
length := s.hashLength |
||||
if length <= 0 || len(hash) < length { |
||||
length = len(hash) |
||||
} |
||||
return hash[:length] |
||||
} |
||||
|
||||
func (s *sanitizer) isInWhitelist(token string) bool { |
||||
return s.whitelist[strings.ToLower(token)] == true |
||||
} |
||||
@ -1,5 +1,5 @@ |
||||
port = 2627 |
||||
sesswatchPeriodSeconds = 120 |
||||
sesshistInitHistorySize = 1000 |
||||
debug = false |
||||
bindControlR = true |
||||
logVerbosity = info |
||||
|
||||
@ -0,0 +1,117 @@ |
||||
package cfg |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"path" |
||||
|
||||
"github.com/BurntSushi/toml" |
||||
"go.uber.org/zap" |
||||
"go.uber.org/zap/zapcore" |
||||
) |
||||
|
||||
// configFile used to parse the config file
|
||||
type configFile struct { |
||||
Port *int |
||||
SesswatchPeriodSeconds *uint |
||||
SesshistInitHistorySize *int |
||||
|
||||
LogLevel *string |
||||
|
||||
BindControlR *bool |
||||
|
||||
// deprecated
|
||||
BindArrowKeysBash *bool |
||||
BindArrowKeysZsh *bool |
||||
Debug *bool |
||||
} |
||||
|
||||
// Config returned by this package to be used in the rest of the project
|
||||
type Config struct { |
||||
Port int |
||||
SesswatchPeriodSeconds uint |
||||
SesshistInitHistorySize int |
||||
LogLevel zapcore.Level |
||||
Debug bool |
||||
BindControlR bool |
||||
} |
||||
|
||||
// defaults for config
|
||||
var defaults = Config{ |
||||
Port: 2627, |
||||
SesswatchPeriodSeconds: 120, |
||||
SesshistInitHistorySize: 1000, |
||||
LogLevel: zap.InfoLevel, |
||||
Debug: false, |
||||
BindControlR: true, |
||||
} |
||||
|
||||
func getConfigPath() (string, error) { |
||||
fname := "resh.toml" |
||||
xdgDir, found := os.LookupEnv("XDG_CONFIG_HOME") |
||||
if found { |
||||
return path.Join(xdgDir, fname), nil |
||||
} |
||||
homeDir, err := os.UserHomeDir() |
||||
if err != nil { |
||||
return "", fmt.Errorf("could not get user home dir: %w", err) |
||||
} |
||||
return path.Join(homeDir, ".config", fname), nil |
||||
} |
||||
|
||||
func readConfig() (*configFile, error) { |
||||
var config configFile |
||||
path, err := getConfigPath() |
||||
if err != nil { |
||||
return &config, fmt.Errorf("could not get config file path: %w", err) |
||||
} |
||||
if _, err := toml.DecodeFile(path, &config); err != nil { |
||||
return &config, fmt.Errorf("could not decode config: %w", err) |
||||
} |
||||
return &config, nil |
||||
} |
||||
|
||||
func processAndFillDefaults(configF *configFile) (Config, error) { |
||||
config := defaults |
||||
|
||||
if configF.Port != nil { |
||||
config.Port = *configF.Port |
||||
} |
||||
if configF.SesswatchPeriodSeconds != nil { |
||||
config.SesswatchPeriodSeconds = *configF.SesswatchPeriodSeconds |
||||
} |
||||
if configF.SesshistInitHistorySize != nil { |
||||
config.SesshistInitHistorySize = *configF.SesshistInitHistorySize |
||||
} |
||||
|
||||
var err error |
||||
if configF.LogLevel != nil { |
||||
logLevel, err := zapcore.ParseLevel(*configF.LogLevel) |
||||
if err != nil { |
||||
err = fmt.Errorf("could not parse log level: %w", err) |
||||
} else { |
||||
config.LogLevel = logLevel |
||||
} |
||||
} |
||||
|
||||
if configF.BindControlR != nil { |
||||
config.BindControlR = *configF.BindControlR |
||||
} |
||||
|
||||
return config, err |
||||
} |
||||
|
||||
// New returns a config file
|
||||
// returned config is always usable, returned errors are informative
|
||||
func New() (Config, error) { |
||||
configF, err := readConfig() |
||||
if err != nil { |
||||
return defaults, fmt.Errorf("using default config because of error while getting config: %w", err) |
||||
} |
||||
|
||||
config, err := processAndFillDefaults(configF) |
||||
if err != nil { |
||||
return config, fmt.Errorf("errors while processing config: %w", err) |
||||
} |
||||
return config, nil |
||||
} |
||||
@ -1,7 +1,7 @@ |
||||
package histcli |
||||
|
||||
import ( |
||||
"github.com/curusarn/resh/pkg/records" |
||||
"github.com/curusarn/resh/internal/records" |
||||
) |
||||
|
||||
// Histcli is a dump of history preprocessed for resh cli purposes
|
||||
@ -0,0 +1,28 @@ |
||||
package logger |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
"go.uber.org/zap" |
||||
"go.uber.org/zap/zapcore" |
||||
) |
||||
|
||||
func New(executable string, level zapcore.Level, developement bool) (*zap.Logger, error) { |
||||
// TODO: consider getting log path from config ?
|
||||
homeDir, err := os.UserHomeDir() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error while getting home dir: %w", err) |
||||
} |
||||
logPath := filepath.Join(homeDir, ".resh/log.json") |
||||
loggerConfig := zap.NewProductionConfig() |
||||
loggerConfig.OutputPaths = []string{logPath} |
||||
loggerConfig.Level.SetLevel(level) |
||||
loggerConfig.Development = developement // DPanic panics in developement
|
||||
logger, err := loggerConfig.Build() |
||||
if err != nil { |
||||
return logger, fmt.Errorf("error while creating logger: %w", err) |
||||
} |
||||
return logger.With(zap.String("executable", executable)), err |
||||
} |
||||
@ -1,6 +1,6 @@ |
||||
package msg |
||||
|
||||
import "github.com/curusarn/resh/pkg/records" |
||||
import "github.com/curusarn/resh/internal/records" |
||||
|
||||
// CliMsg struct
|
||||
type CliMsg struct { |
||||
@ -0,0 +1,69 @@ |
||||
package output |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
|
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
// Output wrapper for writting to logger and stdout/stderr at the same time
|
||||
// useful for errors that should be presented to the user
|
||||
type Output struct { |
||||
Logger *zap.Logger |
||||
ErrPrefix string |
||||
} |
||||
|
||||
func New(logger *zap.Logger, prefix string) *Output { |
||||
return &Output{ |
||||
Logger: logger, |
||||
ErrPrefix: prefix, |
||||
} |
||||
} |
||||
|
||||
func (f *Output) Info(msg string) { |
||||
fmt.Fprintf(os.Stdout, msg) |
||||
f.Logger.Info(msg) |
||||
} |
||||
|
||||
func (f *Output) Error(msg string, err error) { |
||||
fmt.Fprintf(os.Stderr, "%s: %s: %v", f.ErrPrefix, msg, err) |
||||
f.Logger.Error(msg, zap.Error(err)) |
||||
} |
||||
|
||||
func (f *Output) Fatal(msg string, err error) { |
||||
fmt.Fprintf(os.Stderr, "%s: %s: %v", f.ErrPrefix, msg, err) |
||||
f.Logger.Fatal(msg, zap.Error(err)) |
||||
} |
||||
|
||||
var msgDeamonNotRunning = `Resh-daemon didn't respond - it's probably not running. |
||||
|
||||
-> Try restarting this terminal window to bring resh-daemon back up |
||||
-> If the problem persists you can check resh-daemon logs: ~/.resh/log.json |
||||
-> You can create an issue at: https://github.com/curusarn/resh/issues
|
||||
` |
||||
var msgVersionMismatch = `This terminal session was started with different resh version than is installed now. |
||||
It looks like you updated resh and didn't restart this terminal. |
||||
|
||||
-> Restart this terminal window to fix that |
||||
` |
||||
|
||||
func (f *Output) ErrorDaemonNotRunning(err error) { |
||||
fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgDeamonNotRunning) |
||||
f.Logger.Error("Daemon is not running", zap.Error(err)) |
||||
} |
||||
|
||||
func (f *Output) FatalDaemonNotRunning(err error) { |
||||
fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgDeamonNotRunning) |
||||
f.Logger.Fatal("Daemon is not running", zap.Error(err)) |
||||
} |
||||
|
||||
func (f *Output) ErrorVersionMismatch(err error) { |
||||
fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgVersionMismatch) |
||||
f.Logger.Fatal("Version mismatch", zap.Error(err)) |
||||
} |
||||
|
||||
func (f *Output) FatalVersionMismatch(err error) { |
||||
fmt.Fprintf(os.Stderr, "%s: %s", f.ErrPrefix, msgVersionMismatch) |
||||
f.Logger.Fatal("Version mismatch", zap.Error(err)) |
||||
} |
||||
@ -0,0 +1,22 @@ |
||||
package searchapp |
||||
|
||||
import ( |
||||
"github.com/curusarn/resh/internal/histcli" |
||||
"github.com/curusarn/resh/internal/msg" |
||||
"github.com/curusarn/resh/internal/records" |
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
// LoadHistoryFromFile ...
|
||||
func LoadHistoryFromFile(sugar *zap.SugaredLogger, historyPath string, numLines int) msg.CliResponse { |
||||
recs := records.LoadFromFile(sugar, historyPath) |
||||
if numLines != 0 && numLines < len(recs) { |
||||
recs = recs[:numLines] |
||||
} |
||||
cliRecords := histcli.New() |
||||
for i := len(recs) - 1; i >= 0; i-- { |
||||
rec := recs[i] |
||||
cliRecords.AddRecord(rec) |
||||
} |
||||
return msg.CliResponse{CliRecords: cliRecords.List} |
||||
} |
||||
@ -0,0 +1,74 @@ |
||||
package signalhandler |
||||
|
||||
import ( |
||||
"context" |
||||
"net/http" |
||||
"os" |
||||
"os/signal" |
||||
"strconv" |
||||
"syscall" |
||||
"time" |
||||
|
||||
"go.uber.org/zap" |
||||
) |
||||
|
||||
func sendSignals(sugar *zap.SugaredLogger, sig os.Signal, subscribers []chan os.Signal, done chan string) { |
||||
for _, sub := range subscribers { |
||||
sub <- sig |
||||
} |
||||
sugar.Warnw("Sent shutdown signals to components") |
||||
chanCount := len(subscribers) |
||||
start := time.Now() |
||||
delay := time.Millisecond * 100 |
||||
timeout := time.Millisecond * 2000 |
||||
|
||||
for { |
||||
select { |
||||
case _ = <-done: |
||||
chanCount-- |
||||
if chanCount == 0 { |
||||
sugar.Warnw("All components shut down successfully") |
||||
return |
||||
} |
||||
default: |
||||
time.Sleep(delay) |
||||
} |
||||
if time.Since(start) > timeout { |
||||
sugar.Errorw("Timouted while waiting for proper shutdown", |
||||
"componentsStillUp", strconv.Itoa(chanCount), |
||||
"timeout", timeout.String(), |
||||
) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Run catches and handles signals
|
||||
func Run(sugar *zap.SugaredLogger, subscribers []chan os.Signal, done chan string, server *http.Server) { |
||||
sugar = sugar.With("module", "signalhandler") |
||||
signals := make(chan os.Signal, 1) |
||||
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) |
||||
|
||||
var sig os.Signal |
||||
for { |
||||
sig := <-signals |
||||
sugarSig := sugar.With("signal", sig.String()) |
||||
sugarSig.Infow("Got signal") |
||||
if sig == syscall.SIGTERM { |
||||
// Shutdown daemon on SIGTERM
|
||||
break |
||||
} |
||||
sugarSig.Warnw("Ignoring signal. Send SIGTERM to trigger shutdown.") |
||||
} |
||||
|
||||
sugar.Infow("Sending shutdown signals to components ...") |
||||
sendSignals(sugar, sig, subscribers, done) |
||||
|
||||
sugar.Infow("Shutting down the server ...") |
||||
if err := server.Shutdown(context.Background()); err != nil { |
||||
sugar.Errorw("Error while shuting down HTTP server", |
||||
"error", err, |
||||
) |
||||
} |
||||
} |
||||
@ -1,12 +0,0 @@ |
||||
package cfg |
||||
|
||||
// Config struct
|
||||
type Config struct { |
||||
Port int |
||||
SesswatchPeriodSeconds uint |
||||
SesshistInitHistorySize int |
||||
Debug bool |
||||
BindArrowKeysBash bool |
||||
BindArrowKeysZsh bool |
||||
BindControlR bool |
||||
} |
||||
@ -1,21 +0,0 @@ |
||||
package searchapp |
||||
|
||||
import ( |
||||
"github.com/curusarn/resh/pkg/histcli" |
||||
"github.com/curusarn/resh/pkg/msg" |
||||
"github.com/curusarn/resh/pkg/records" |
||||
) |
||||
|
||||
// LoadHistoryFromFile ...
|
||||
func LoadHistoryFromFile(historyPath string, numLines int) msg.CliResponse { |
||||
recs := records.LoadFromFile(historyPath) |
||||
if numLines != 0 && numLines < len(recs) { |
||||
recs = recs[:numLines] |
||||
} |
||||
cliRecords := histcli.New() |
||||
for i := len(recs) - 1; i >= 0; i-- { |
||||
rec := recs[i] |
||||
cliRecords.AddRecord(rec) |
||||
} |
||||
return msg.CliResponse{CliRecords: cliRecords.List} |
||||
} |
||||
@ -1,65 +0,0 @@ |
||||
package signalhandler |
||||
|
||||
import ( |
||||
"context" |
||||
"log" |
||||
"net/http" |
||||
"os" |
||||
"os/signal" |
||||
"strconv" |
||||
"syscall" |
||||
"time" |
||||
) |
||||
|
||||
func sendSignals(sig os.Signal, subscribers []chan os.Signal, done chan string) { |
||||
for _, sub := range subscribers { |
||||
sub <- sig |
||||
} |
||||
chanCount := len(subscribers) |
||||
start := time.Now() |
||||
delay := time.Millisecond * 100 |
||||
timeout := time.Millisecond * 2000 |
||||
|
||||
for { |
||||
select { |
||||
case _ = <-done: |
||||
chanCount-- |
||||
if chanCount == 0 { |
||||
log.Println("signalhandler: All components shut down successfully") |
||||
return |
||||
} |
||||
default: |
||||
time.Sleep(delay) |
||||
} |
||||
if time.Since(start) > timeout { |
||||
log.Println("signalhandler: Timouted while waiting for proper shutdown - " + strconv.Itoa(chanCount) + " boxes are up after " + timeout.String()) |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Run catches and handles signals
|
||||
func Run(subscribers []chan os.Signal, done chan string, server *http.Server) { |
||||
signals := make(chan os.Signal, 1) |
||||
|
||||
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) |
||||
|
||||
var sig os.Signal |
||||
for { |
||||
sig := <-signals |
||||
log.Printf("signalhandler: Got signal '%s'\n", sig.String()) |
||||
if sig == syscall.SIGTERM { |
||||
// Shutdown daemon on SIGTERM
|
||||
break |
||||
} |
||||
log.Printf("signalhandler: Ignoring signal '%s'. Send SIGTERM to trigger shutdown.\n", sig.String()) |
||||
} |
||||
|
||||
log.Println("signalhandler: Sending shutdown signals to components") |
||||
sendSignals(sig, subscribers, done) |
||||
|
||||
log.Println("signalhandler: Shutting down the server") |
||||
if err := server.Shutdown(context.Background()); err != nil { |
||||
log.Printf("HTTP server Shutdown: %v", err) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue