Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c655abae2 | ||
|
|
b87cc6fb62 | ||
|
|
96f551be80 | ||
|
|
73ab6405aa | ||
|
|
8fa62f9b91 |
No files matched your search
@@ -28,3 +28,23 @@ All notable changes to Guard are documented here. The format follows
|
|||||||
the `adm` and `systemd-journal` groups, published with each release to
|
the `adm` and `systemd-journal` groups, published with each release to
|
||||||
the Gitea Debian registry (`stable`, or `testing` for a release
|
the Gitea Debian registry (`stable`, or `testing` for a release
|
||||||
candidate) along with tarballs for Linux amd64 and arm64.
|
candidate) along with tarballs for Linux amd64 and arm64.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The package's `guard` user is now in the `adm` and `systemd-journal`
|
||||||
|
groups itself, as the documentation said, not only through its unit's
|
||||||
|
`SupplementaryGroups`. `sudo -u guard guard test` and `guard check`
|
||||||
|
could read neither `/var/log`'s files (`permission denied`) nor the
|
||||||
|
journal. Upgrading adds the groups.
|
||||||
|
- Shorter, plainer command output. Tables name a journal source by its
|
||||||
|
first two units or identifiers (`journal postfix/smtpd,
|
||||||
|
postfix/submission/smtpd +1`), so `presets`, `check` and `status`
|
||||||
|
fit a normal terminal. `guard test` prints the first 20 matching
|
||||||
|
lines (not 200) and the first 20 addresses it would ban (`--all` for
|
||||||
|
every one), single addresses without `/32`, and counts the matches
|
||||||
|
from `never_ban` addresses. When Housecarl doesn't answer, `guard check`
|
||||||
|
says so once ("Housecarl at URL doesn't answer: connection refused")
|
||||||
|
instead of once per ban list with the request's URL, and only asks for
|
||||||
|
`log:read` when Housecarl refused the log. `guard status` tells "not
|
||||||
|
running" from "no permission". `guard bans` says when there are none;
|
||||||
|
`guard unban` says "1 ban lifted".
|
||||||
@@ -65,4 +65,4 @@ Housecarl's side: its [docs/bans.md](https://gitea.adbraun.lol/adam/housecarl/sr
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Guard 0.1.0 is in testing (0.1.0-rc.1). Go, `CGO_ENABLED=0`, Linux. MIT licence.
|
Guard 0.1.0 is in testing (0.1.0-rc.1+git7-b87cc6f). Go, `CGO_ENABLED=0`, Linux. MIT licence.
|
||||||
+100
-29
@@ -14,6 +14,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -161,7 +162,7 @@ func checkCmd() *cobra.Command {
|
|||||||
}
|
}
|
||||||
lists[x.Ban.List] = true
|
lists[x.Ban.List] = true
|
||||||
drops = drops || x.Kind() == config.SourceHousecarlDrops
|
drops = drops || x.Kind() == config.SourceHousecarlDrops
|
||||||
fmt.Fprintf(w, "%s\t%s\t%d\t%d in %s\t%s\t%s\n", x.Name, daemon.Describe(x), len(x.Match), x.Hits, x.Within, x.Ban.List, ports(x.Ban.Protocol, x.Ban.Port))
|
fmt.Fprintf(w, "%s\t%s\t%d\t%d in %s\t%s\t%s\n", x.Name, daemon.Short(x), len(x.Match), x.Hits, x.Within, x.Ban.List, ports(x.Ban.Protocol, x.Ban.Port))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
hc, err := client(cfg)
|
hc, err := client(cfg)
|
||||||
@@ -169,9 +170,17 @@ func checkCmd() *cobra.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
bad := false
|
bad := false
|
||||||
|
names := make([]string, 0, len(lists))
|
||||||
for l := range lists {
|
for l := range lists {
|
||||||
|
names = append(names, l)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
for _, l := range names {
|
||||||
bl, err := hc.BanList(ctx(), l)
|
bl, err := hc.BanList(ctx(), l)
|
||||||
|
var down *housecarl.UnreachableError
|
||||||
switch {
|
switch {
|
||||||
|
case errors.As(err, &down):
|
||||||
|
return err
|
||||||
case err != nil:
|
case err != nil:
|
||||||
fmt.Printf("ban list %s: %v\n", l, err)
|
fmt.Printf("ban list %s: %v\n", l, err)
|
||||||
bad = true
|
bad = true
|
||||||
@@ -183,7 +192,11 @@ func checkCmd() *cobra.Command {
|
|||||||
}
|
}
|
||||||
if drops {
|
if drops {
|
||||||
if _, err := hc.Log(ctx(), time.Now()); err != nil {
|
if _, err := hc.Log(ctx(), time.Now()); err != nil {
|
||||||
fmt.Printf("Housecarl's log (housecarl-drops): %v (the token needs log:read)\n", err)
|
hint := ""
|
||||||
|
if housecarl.Refused(err) {
|
||||||
|
hint = " (the token needs log:read)"
|
||||||
|
}
|
||||||
|
fmt.Printf("Housecarl's log (housecarl-drops): %v%s\n", err, hint)
|
||||||
bad = true
|
bad = true
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Housecarl's log (housecarl-drops): ok")
|
fmt.Println("Housecarl's log (housecarl-drops): ok")
|
||||||
@@ -266,7 +279,7 @@ func presetsCmd() *cobra.Command {
|
|||||||
fmt.Fprintln(w, "PRESET\tSOURCE\tWHAT")
|
fmt.Fprintln(w, "PRESET\tSOURCE\tWHAT")
|
||||||
for _, n := range names {
|
for _, n := range names {
|
||||||
p := ps[n]
|
p := ps[n]
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\n", n, daemon.Describe(&config.Watch{File: p.File, Journal: p.Journal, Source: p.Source, Protocol: "tcp"}), p.Description)
|
fmt.Fprintf(w, "%s\t%s\t%s\n", n, daemon.Short(&config.Watch{File: p.File, Journal: p.Journal, Source: p.Source, Protocol: "tcp"}), p.Description)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
@@ -331,10 +344,14 @@ the watch's window (within) is used.`,
|
|||||||
count int
|
count int
|
||||||
ban bool
|
ban bool
|
||||||
}
|
}
|
||||||
|
type found struct {
|
||||||
|
addr, text, why string
|
||||||
|
}
|
||||||
hits := map[string]*hit{}
|
hits := map[string]*hit{}
|
||||||
order := []string{}
|
order := []string{}
|
||||||
tr := map[string][]time.Time{}
|
tr := map[string][]time.Time{}
|
||||||
matched := 0
|
var shown []found
|
||||||
|
matched, spared := 0, 0
|
||||||
for _, l := range in {
|
for _, l := range in {
|
||||||
a, ok := m.Match(l.Text)
|
a, ok := m.Match(l.Text)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -342,17 +359,14 @@ the watch's window (within) is used.`,
|
|||||||
}
|
}
|
||||||
matched++
|
matched++
|
||||||
why := never.Why(a)
|
why := never.Why(a)
|
||||||
if all || matched <= 200 {
|
if all || len(shown) < testShown {
|
||||||
note := ""
|
shown = append(shown, found{a.String(), l.Text, why})
|
||||||
if why != "" {
|
|
||||||
note = " (never banned: " + why + ")"
|
|
||||||
}
|
|
||||||
fmt.Printf("%-39s %s%s\n", a, clip(l.Text, 160), note)
|
|
||||||
}
|
}
|
||||||
if why != "" {
|
if why != "" {
|
||||||
|
spared++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
g := match.Group(a, 64).String()
|
g := address(match.Group(a, 64))
|
||||||
h := hits[g]
|
h := hits[g]
|
||||||
if h == nil {
|
if h == nil {
|
||||||
h = &hit{}
|
h = &hit{}
|
||||||
@@ -371,39 +385,72 @@ the watch's window (within) is used.`,
|
|||||||
h.ban = h.ban || h.count >= w.Hits
|
h.ban = h.ban || h.count >= w.Hits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if matched > 200 && !all {
|
width := 0
|
||||||
fmt.Printf("… %d more matching lines (--all shows them)\n", matched-200)
|
for _, f := range shown {
|
||||||
|
width = max(width, len(f.addr))
|
||||||
}
|
}
|
||||||
fmt.Printf("\n%d lines, %d matched, %d addresses (IPv6 by /64)\n", len(in), matched, len(order))
|
for _, f := range shown {
|
||||||
|
note := ""
|
||||||
|
if f.why != "" {
|
||||||
|
note = " (never banned: " + f.why + ")"
|
||||||
|
}
|
||||||
|
fmt.Printf("%-*s %s%s\n", width, f.addr, clip(f.text, 120), note)
|
||||||
|
}
|
||||||
|
if matched > len(shown) {
|
||||||
|
fmt.Printf("… and %d more matching lines (--all shows them)\n", matched-len(shown))
|
||||||
|
}
|
||||||
|
fmt.Printf("\n%d lines, %d matched, %s (IPv6 by /64)", len(in), matched, plural(len(order), "address"))
|
||||||
|
if spared > 0 {
|
||||||
|
fmt.Printf("; %d matches from never_ban addresses", spared)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
sort.SliceStable(order, func(i, j int) bool { return hits[order[i]].count > hits[order[j]].count })
|
sort.SliceStable(order, func(i, j int) bool { return hits[order[i]].count > hits[order[j]].count })
|
||||||
banned := 0
|
var banned []string
|
||||||
for _, g := range order {
|
for _, g := range order {
|
||||||
if hits[g].ban {
|
if hits[g].ban {
|
||||||
banned++
|
banned = append(banned, g)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if banned == 0 {
|
rule := fmt.Sprintf("%d hits within %s", w.Hits, w.Within)
|
||||||
fmt.Printf("nobody would be banned (%d hits within %s)\n", w.Hits, w.Within)
|
if !timed {
|
||||||
|
rule = fmt.Sprintf("%d hits; a file's lines have no times, so all of them count", w.Hits)
|
||||||
|
}
|
||||||
|
if len(banned) == 0 {
|
||||||
|
fmt.Printf("Nobody would be banned (%s)\n", rule)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
window := "within " + w.Within.String()
|
fmt.Printf("Would ban %d (%s):\n", len(banned), rule)
|
||||||
if !timed {
|
width = 0
|
||||||
window = "in all (a file's lines have no time)"
|
for _, g := range banned {
|
||||||
|
width = max(width, len(g))
|
||||||
}
|
}
|
||||||
fmt.Printf("would ban %d (%d hits %s):\n", banned, w.Hits, window)
|
for i, g := range banned {
|
||||||
for _, g := range order {
|
if i == testShown && !all {
|
||||||
if hits[g].ban {
|
fmt.Printf(" … and %d more (--all shows them)\n", len(banned)-i)
|
||||||
fmt.Printf(" %-39s %d matches\n", g, hits[g].count)
|
break
|
||||||
}
|
}
|
||||||
|
fmt.Printf(" %-*s %s\n", width, g, plural(hits[g].count, "match"))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().IntVarP(&lines, "lines", "n", 5000, "a journal watch without FILE: the last this many entries")
|
cmd.Flags().IntVarP(&lines, "lines", "n", 5000, "a journal watch without FILE: the last this many entries")
|
||||||
cmd.Flags().BoolVar(&all, "all", false, "print every matching line, not the first 200")
|
cmd.Flags().BoolVar(&all, "all", false, "print every matching line and address, not the first 20")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testShown is how many matching lines, and addresses to ban, guard
|
||||||
|
// test prints without --all.
|
||||||
|
const testShown = 20
|
||||||
|
|
||||||
|
// address is a ban's address: a single address without its /32 or /128.
|
||||||
|
func address(p netip.Prefix) string {
|
||||||
|
if p.IsSingleIP() {
|
||||||
|
return p.Addr().String()
|
||||||
|
}
|
||||||
|
return p.String()
|
||||||
|
}
|
||||||
|
|
||||||
func readLines(path string) ([]source.Line, error) {
|
func readLines(path string) ([]source.Line, error) {
|
||||||
var r io.Reader = os.Stdin
|
var r io.Reader = os.Stdin
|
||||||
if path != "-" {
|
if path != "-" {
|
||||||
@@ -473,8 +520,17 @@ func statusCmd() *cobra.Command {
|
|||||||
return d.DialContext(c, "unix", cfg.Socket)
|
return d.DialContext(c, "unix", cfg.Socket)
|
||||||
}}}
|
}}}
|
||||||
resp, err := hc.Get("http://guard/status")
|
resp, err := hc.Get("http://guard/status")
|
||||||
if err != nil {
|
switch {
|
||||||
return fmt.Errorf("Guard isn't running, or %s can't be reached (as root, or the guard user): %w", cfg.Socket, err)
|
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)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
var s daemon.Status
|
var s daemon.Status
|
||||||
@@ -519,6 +575,17 @@ func statusCmd() *cobra.Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// plural is "1 ban", "2 bans", "2 matches".
|
||||||
|
func plural(n int, what string) string {
|
||||||
|
if n == 1 {
|
||||||
|
return "1 " + what
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(what, "s") || strings.HasSuffix(what, "ch") {
|
||||||
|
return strconv.Itoa(n) + " " + what + "es"
|
||||||
|
}
|
||||||
|
return strconv.Itoa(n) + " " + what + "s"
|
||||||
|
}
|
||||||
|
|
||||||
func splitComma(s string) []string {
|
func splitComma(s string) []string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return nil
|
return nil
|
||||||
@@ -562,6 +629,10 @@ func bansCmd() *cobra.Command {
|
|||||||
if flagJSON {
|
if flagJSON {
|
||||||
return printJSON(bans)
|
return printJSON(bans)
|
||||||
}
|
}
|
||||||
|
if len(bans) == 0 {
|
||||||
|
fmt.Printf("No active bans in %s\n", l)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
table(func(w io.Writer) {
|
table(func(w io.Writer) {
|
||||||
fmt.Fprintln(w, "ID\tADDRESS\tPORT\tENDS IN\tBY\tSOURCE")
|
fmt.Fprintln(w, "ID\tADDRESS\tPORT\tENDS IN\tBY\tSOURCE")
|
||||||
for _, b := range bans {
|
for _, b := range bans {
|
||||||
@@ -668,7 +739,7 @@ func unbanCmd() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("%d bans lifted\n", n)
|
fmt.Printf("%s lifted\n", plural(n, "ban"))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
# Guard's system user (systemd-sysusers): it reads logs and talks to
|
# Guard's system user (systemd-sysusers): it reads logs and talks to
|
||||||
# Housecarl, nothing more.
|
# Housecarl, nothing more. adm reads /var/log's files, systemd-journal
|
||||||
|
# the journal, for the daemon (its unit adds them too) and for
|
||||||
|
# 'sudo -u guard guard test'.
|
||||||
u guard - "Guard log watcher" /var/lib/guard /usr/sbin/nologin
|
u guard - "Guard log watcher" /var/lib/guard /usr/sbin/nologin
|
||||||
|
m guard adm
|
||||||
|
m guard systemd-journal
|
||||||
@@ -173,6 +173,28 @@ func Describe(w *config.Watch) string {
|
|||||||
return "file " + w.File
|
return "file " + w.File
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Short describes a watch's source in a table's few columns: at most
|
||||||
|
// two journal units or identifiers, then how many more.
|
||||||
|
func Short(w *config.Watch) string {
|
||||||
|
switch w.Kind() {
|
||||||
|
case "journal":
|
||||||
|
names := w.Journal.Unit
|
||||||
|
if len(names) == 0 {
|
||||||
|
names = w.Journal.Identifier
|
||||||
|
} else {
|
||||||
|
names = append(append([]string(nil), names...), w.Journal.Identifier...)
|
||||||
|
}
|
||||||
|
s := "journal " + strings.Join(names[:min(len(names), 2)], ", ")
|
||||||
|
if len(names) > 2 {
|
||||||
|
s += fmt.Sprintf(" +%d", len(names)-2)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
case config.SourceHousecarlDrops:
|
||||||
|
return "Housecarl drops (" + w.Protocol + ")"
|
||||||
|
}
|
||||||
|
return "file " + w.File
|
||||||
|
}
|
||||||
|
|
||||||
// loadGroups asks Housecarl how each ban list groups IPv6 addresses, so
|
// loadGroups asks Housecarl how each ban list groups IPv6 addresses, so
|
||||||
// bounties are counted as the list bans. A list that can't be read now
|
// bounties are counted as the list bans. A list that can't be read now
|
||||||
// counts by /64, its default.
|
// counts by /64, its default.
|
||||||
@@ -409,7 +431,7 @@ type WatchStatus struct {
|
|||||||
func (d *Daemon) Status() Status {
|
func (d *Daemon) Status() Status {
|
||||||
s := Status{Started: d.started}
|
s := Status{Started: d.started}
|
||||||
for _, w := range d.watches {
|
for _, w := range d.watches {
|
||||||
s.Watches = append(s.Watches, WatchStatus{Name: w.Cfg.Name, Source: Describe(w.Cfg), List: w.Cfg.Ban.List, Hits: w.Cfg.Hits,
|
s.Watches = append(s.Watches, WatchStatus{Name: w.Cfg.Name, Source: Short(w.Cfg), List: w.Cfg.Ban.List, Hits: w.Cfg.Hits,
|
||||||
Within: w.Cfg.Within.String(), Lines: w.lines.Load(), Matches: w.matches.Load(), Bans: w.bans.Load(), Top: w.Tracker.Top(10, d.Now())})
|
Within: w.Cfg.Within.String(), Lines: w.lines.Load(), Matches: w.matches.Load(), Bans: w.bans.Load(), Top: w.Tracker.Top(10, d.Now())})
|
||||||
}
|
}
|
||||||
s.Recent, s.Pending = d.state.Snapshot()
|
s.Recent, s.Pending = d.state.Snapshot()
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ const Prefix = "/api/v1"
|
|||||||
|
|
||||||
// Client talks to one Housecarl.
|
// Client talks to one Housecarl.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
base string
|
base string
|
||||||
token string
|
socket string
|
||||||
http *http.Client
|
token string
|
||||||
|
http *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options say how to reach Housecarl.
|
// Options say how to reach Housecarl.
|
||||||
@@ -72,7 +73,7 @@ func New(o Options) (*Client, error) {
|
|||||||
}
|
}
|
||||||
tr.TLSClientConfig = cfg
|
tr.TLSClientConfig = cfg
|
||||||
}
|
}
|
||||||
return &Client{base: base, token: strings.TrimSpace(o.Token), http: &http.Client{Transport: tr, Timeout: timeout}}, nil
|
return &Client{base: base, socket: o.Socket, token: strings.TrimSpace(o.Token), http: &http.Client{Transport: tr, Timeout: timeout}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error is an error answer from Housecarl.
|
// Error is an error answer from Housecarl.
|
||||||
@@ -89,6 +90,32 @@ func (e *Error) Error() string {
|
|||||||
return e.Message
|
return e.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnreachableError is a request Housecarl never answered: no
|
||||||
|
// connection, a TLS failure, a timeout.
|
||||||
|
type UnreachableError struct {
|
||||||
|
Where string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnreachableError) Error() string {
|
||||||
|
err := e.Err
|
||||||
|
var ue *url.Error
|
||||||
|
if errors.As(err, &ue) {
|
||||||
|
err = ue.Err // without the method and the URL with its query
|
||||||
|
}
|
||||||
|
return "Housecarl at " + e.Where + " doesn't answer: " + err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnreachableError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
|
// where is where the client reaches Housecarl, for messages.
|
||||||
|
func (c *Client) where() string {
|
||||||
|
if c.socket != "" {
|
||||||
|
return c.socket
|
||||||
|
}
|
||||||
|
return c.base
|
||||||
|
}
|
||||||
|
|
||||||
// Refused reports whether Housecarl refused a request for good: asking
|
// Refused reports whether Housecarl refused a request for good: asking
|
||||||
// again won't help (a bad request, no permission, an address never
|
// again won't help (a bad request, no permission, an address never
|
||||||
// banned). Anything else (no answer, a server error) is worth retrying.
|
// banned). Anything else (no answer, a server error) is worth retrying.
|
||||||
@@ -123,7 +150,7 @@ func (c *Client) do(ctx context.Context, method, path string, query url.Values,
|
|||||||
}
|
}
|
||||||
resp, err := c.http.Do(req)
|
resp, err := c.http.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return &UnreachableError{Where: c.where(), Err: err}
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
data, _ := io.ReadAll(io.LimitReader(resp.Body, 16<<20))
|
data, _ := io.ReadAll(io.LimitReader(resp.Body, 16<<20))
|
||||||
|
|||||||
+2
-2
@@ -142,10 +142,10 @@ not_contains "$NET2"
|
|||||||
run "ssh from it still works" 0 connect 22 $NET2
|
run "ssh from it still works" 0 connect 22 $NET2
|
||||||
contains "^ssh$"
|
contains "^ssh$"
|
||||||
run "guard test over the log" 0 docker exec ${P}-srv /w/guard -c /w/guard.yaml test ssh /w/auth.log
|
run "guard test over the log" 0 docker exec ${P}-srv /w/guard -c /w/guard.yaml test ssh /w/auth.log
|
||||||
contains "would ban 1"
|
contains "Would ban 1"
|
||||||
contains "never banned: in never_ban $NET2/32"
|
contains "never banned: in never_ban $NET2/32"
|
||||||
run "guard unban" 0 docker exec ${P}-srv /w/guard -c /w/guard.yaml unban $NET
|
run "guard unban" 0 docker exec ${P}-srv /w/guard -c /w/guard.yaml unban $NET
|
||||||
contains "1 bans lifted"
|
contains "1 ban lifted"
|
||||||
run "ssh again" 0 connect 22
|
run "ssh again" 0 connect 22
|
||||||
contains "^ssh$"
|
contains "^ssh$"
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ done
|
|||||||
files="$files dist/SHA256SUMS"
|
files="$files dist/SHA256SUMS"
|
||||||
|
|
||||||
# The release (created, or found when publishing again).
|
# The release (created, or found when publishing again).
|
||||||
release=$(call 404 "$api/releases/tags/$tag")
|
release=$(call 404 "$api/releases/tags/$(printf '%s' "$tag" | sed 's/+/%2B/g')")
|
||||||
id=$(printf '%s\n' "$release" | jq -r '.id // empty')
|
id=$(printf '%s\n' "$release" | jq -r '.id // empty')
|
||||||
if [ -z "$id" ]; then
|
if [ -z "$id" ]; then
|
||||||
body=$(jq -n --arg tag "$tag" --arg notes "$notes" --argjson pre "$prerelease" \
|
body=$(jq -n --arg tag "$tag" --arg notes "$notes" --argjson pre "$prerelease" \
|
||||||
@@ -127,7 +127,8 @@ for f in $files; do
|
|||||||
name=${f#dist/}
|
name=${f#dist/}
|
||||||
old=$(printf '%s\n' "$release" | jq -r --arg n "$name" '.assets[]? | select(.name == $n) | .id')
|
old=$(printf '%s\n' "$release" | jq -r --arg n "$name" '.assets[]? | select(.name == $n) | .id')
|
||||||
[ -z "$old" ] || call "" -X DELETE "$api/releases/$id/assets/$old" >/dev/null
|
[ -z "$old" ] || call "" -X DELETE "$api/releases/$id/assets/$old" >/dev/null
|
||||||
call "" -X POST -F "attachment=@$f" "$api/releases/$id/assets?name=$name" >/dev/null
|
# A + (build metadata, 0.1.0-rc.1+git4-8fa62f9) would be a space in a query.
|
||||||
|
call "" -X POST -F "attachment=@$f" "$api/releases/$id/assets?name=$(printf '%s' "$name" | sed 's/+/%2B/g')" >/dev/null
|
||||||
echo "gitea-release: attached $name"
|
echo "gitea-release: attached $name"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user