nginx-cache-purge/flags.go
James Coleman 8d9b1c9302 Add service management, harden the purge server, and modernize the build.
Server:
- Add a --cache-path allowlist so a server can be limited to the caches it
  is meant to purge, defaulting to any path as before.
- Set socket permissions explicitly (--socket-mode, default 0660) instead of
  inheriting the service manager's umask, which left the socket unreachable.
- Refuse to remove a socket another instance is still serving.
- Read keys both raw and decoded, so keys nginx stored with escapes and keys
  a caller escaped by hand both purge.
- Add an exact= parameter for literal keys containing glob punctuation.
- Report purge failures as 500 rather than 502, and send error bodies through
  http.Error so a failure is not reported as a successful purge.
- Graceful shutdown with systemd readiness notification.

Purge:
- Group purge arguments into PurgeRequest and report the number of entries
  removed.
- Compile exclude globs once, and fail the purge when one is invalid rather
  than purging the keys it was meant to keep.
- Cap header scanning and tolerate entries nginx evicts mid-walk.
- Switch to filepath.WalkDir to avoid an Lstat per cache file.

New:
- service command to install, start, stop, and remove the system service.
- service install takes --cache-path, writing the allowlist into the unit it
  installs, so an installed service is restricted from its first start.
- Makefile, VERSION, and build identifiers stamped via ldflags.
- Tests for the server handler and the service command.

Build:
- Update to Go 1.25, kong v1, GoReleaser v2, and current GitHub Actions.
- Add vet and test steps to CI.
- Rename purgeCmd.go/serverCmd.go to Go's file naming convention.

Bump version to 0.2.0.
2026-08-12 14:58:12 -05:00

81 lines
2.2 KiB
Go

package main
import (
"fmt"
"runtime/debug"
"strings"
"github.com/alecthomas/kong"
)
// VersionFlag prints build information and exits.
type VersionFlag bool
// Decode satisfies kong.MapperValue. The flag is treated as a boolean toggle.
func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil }
// IsBool reports the flag as a boolean for kong's parser.
func (v VersionFlag) IsBool() bool { return true }
// BeforeApply emits version information then exits before the rest of the
// command is executed.
func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error {
fmt.Printf("%s: %s (%s)\n", Name, Version, Mode)
if Commit != "" {
fmt.Printf(" commit: %s\n", Commit)
}
if Date != "" {
fmt.Printf(" built: %s\n", Date)
}
// Without build stamps, a module-aware build still records the revision
// it was built from, so fall back to what the toolchain embedded.
if Commit == "" {
if bi, ok := debug.ReadBuildInfo(); ok {
for _, s := range bi.Settings {
switch s.Key {
case "vcs.revision":
fmt.Printf(" commit: %s\n", s.Value)
case "vcs.time":
fmt.Printf(" built: %s\n", s.Value)
}
}
}
}
app.Exit(0)
return nil
}
// Flags and or commands supplied to cli.
type Flags struct {
Version VersionFlag `name:"version" help:"Print version information and quit"`
Server ServerCmd `cmd:"" aliases:"s" default:"1" help:"Run the server"`
Purge PurgeCmd `cmd:"" aliases:"p" help:"Purge cache now"`
Service ServiceCmd `cmd:"" help:"Manage the purge server system service."`
}
// kongVars are the values the command tags interpolate. They live in one place
// so that a parser built anywhere describes the same command line.
func kongVars() kong.Vars {
return kong.Vars{
"serviceActions": strings.Join(ServiceAction, ","),
"defaultSocket": defaultSocketPath,
"defaultMode": defaultSocketMode,
}
}
// Parse the supplied flags and commands.
func (a *App) ParseFlags() *kong.Context {
a.flags = &Flags{}
ctx := kong.Parse(a.flags,
kong.Name(Name),
kong.Description(Description),
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
}),
kongVars(),
)
return ctx
}