Reload guard.yaml without losing the bounties
systemctl reload guard, guard reload or SIGHUP reads the configuration again: a watch keeps its bounties and counts under the same name and takes its new settings at once; its source starts again only if it changed; watches gone stop, new ones start; a file with a mistake is refused. The Housecarl connection, state, socket and log wait for a restart, and guard reload says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9c655abae2
commit
ecf4129877
11 files changed
+607
-93
No files matched your search
+86
-17
@@ -53,6 +53,7 @@ keeps it from this machine (or from what the list says).
|
||||
guard check check guard.yaml and reach Housecarl
|
||||
guard test ssh /var/log/auth.log what a watch's patterns find in a log
|
||||
guard status the watches, their highest bounties, recent bans
|
||||
guard reload read guard.yaml again, keeping the bounties
|
||||
|
||||
Exit codes: 0 ok, 1 error.`,
|
||||
SilenceUsage: true,
|
||||
@@ -61,7 +62,7 @@ Exit codes: 0 ok, 1 error.`,
|
||||
}
|
||||
root.PersistentFlags().StringVarP(&flagConfig, "config", "c", config.DefaultPath, "configuration file")
|
||||
root.PersistentFlags().BoolVar(&flagJSON, "json", false, "print JSON instead of tables")
|
||||
root.AddCommand(runCmd(), checkCmd(), configCmd(), presetsCmd(), testCmd(), statusCmd(), bansCmd(), banCmd(), unbanCmd(), versionCmd())
|
||||
root.AddCommand(runCmd(), checkCmd(), configCmd(), presetsCmd(), testCmd(), statusCmd(), reloadCmd(), bansCmd(), banCmd(), unbanCmd(), versionCmd())
|
||||
if err := root.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "guard:", err)
|
||||
os.Exit(1)
|
||||
@@ -132,8 +133,19 @@ func runCmd() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Load = load
|
||||
c, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
// SIGHUP reloads, as guard reload does.
|
||||
hup := make(chan os.Signal, 1)
|
||||
signal.Notify(hup, syscall.SIGHUP)
|
||||
go func() {
|
||||
for range hup {
|
||||
if _, err := d.ReloadFile(); err != nil {
|
||||
log.Error("reload refused; the configuration stays as it was", "err", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
log.Info("starting", "version", version.String(), "config", flagConfig)
|
||||
return d.Run(c)
|
||||
},
|
||||
@@ -507,6 +519,32 @@ func clip(s string, n int) string {
|
||||
|
||||
// ---- status
|
||||
|
||||
// askDaemon makes a request of the running Guard, on its socket.
|
||||
func askDaemon(cfg *config.Config, method, path string) (*http.Response, error) {
|
||||
hc := &http.Client{Timeout: 30 * time.Second, Transport: &http.Transport{DialContext: func(c context.Context, _, _ string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
return d.DialContext(c, "unix", cfg.Socket)
|
||||
}}}
|
||||
req, err := http.NewRequest(method, "http://guard"+path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := hc.Do(req)
|
||||
switch {
|
||||
case errors.Is(err, os.ErrNotExist), errors.Is(err, syscall.ECONNREFUSED):
|
||||
return nil, fmt.Errorf("Guard isn't running (nothing answers on %s)", cfg.Socket)
|
||||
case errors.Is(err, os.ErrPermission):
|
||||
return nil, fmt.Errorf("no permission for %s: run this as root or the guard user", cfg.Socket)
|
||||
case err != nil:
|
||||
var ue *url.Error
|
||||
if errors.As(err, &ue) {
|
||||
err = ue.Err
|
||||
}
|
||||
return nil, fmt.Errorf("Guard's socket %s: %w", cfg.Socket, err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func statusCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "status", Short: "The running Guard: its watches, highest bounties, recent bans", Args: cobra.NoArgs,
|
||||
@@ -515,22 +553,9 @@ func statusCmd() *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hc := &http.Client{Timeout: 10 * time.Second, Transport: &http.Transport{DialContext: func(c context.Context, _, _ string) (net.Conn, error) {
|
||||
var d net.Dialer
|
||||
return d.DialContext(c, "unix", cfg.Socket)
|
||||
}}}
|
||||
resp, err := hc.Get("http://guard/status")
|
||||
switch {
|
||||
case errors.Is(err, os.ErrNotExist), errors.Is(err, syscall.ECONNREFUSED):
|
||||
return fmt.Errorf("Guard isn't running (nothing answers on %s)", cfg.Socket)
|
||||
case errors.Is(err, os.ErrPermission):
|
||||
return fmt.Errorf("no permission for %s: run this as root or the guard user", cfg.Socket)
|
||||
case err != nil:
|
||||
var ue *url.Error
|
||||
if errors.As(err, &ue) {
|
||||
err = ue.Err
|
||||
}
|
||||
return fmt.Errorf("Guard's socket %s: %w", cfg.Socket, err)
|
||||
resp, err := askDaemon(cfg, http.MethodGet, "/status")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var s daemon.Status
|
||||
@@ -575,6 +600,50 @@ func statusCmd() *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- reload
|
||||
|
||||
func reloadCmd() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "reload", Short: "Make the running Guard read guard.yaml again, keeping its bounties", Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Checked here first, so a mistake is shown before Guard sees it.
|
||||
cfg, err := load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := askDaemon(cfg, http.MethodPost, "/reload")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
var e struct{ Error string }
|
||||
json.NewDecoder(resp.Body).Decode(&e)
|
||||
return fmt.Errorf("reload refused: %s", e.Error)
|
||||
}
|
||||
var r daemon.Reloaded
|
||||
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||
return err
|
||||
}
|
||||
if flagJSON {
|
||||
return printJSON(r)
|
||||
}
|
||||
if len(r.Changes) == 0 {
|
||||
fmt.Println("Reloaded: nothing changed")
|
||||
} else {
|
||||
fmt.Println("Reloaded:")
|
||||
for _, c := range r.Changes {
|
||||
fmt.Println(" " + c)
|
||||
}
|
||||
}
|
||||
if len(r.Restart) > 0 {
|
||||
fmt.Printf("Changed, but only on a restart (systemctl restart guard): %s\n", strings.Join(r.Restart, ", "))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// plural is "1 ban", "2 bans", "2 matches".
|
||||
func plural(n int, what string) string {
|
||||
if n == 1 {
|
||||
|
||||
Reference in new issue
Block a user