Change --info to --api; /index/rebuild now requires POST instead of GET request; merge --ignore and --ignore-file

This commit is contained in:
Seednode 2024-01-30 10:12:05 -06:00
parent 3a6aaee236
commit 83809696ac
6 changed files with 70 additions and 69 deletions

View File

@ -20,7 +20,7 @@ Dockerfile available [here](https://git.seedno.de/seednode/roulette/raw/branch/m
An example instance with most features enabled can be found [here](https://nature.seedno.de/). An example instance with most features enabled can be found [here](https://nature.seedno.de/).
## Admin prefix ## Admin prefix
You can restrict access to certain functionality by prepending a secret string to the paths. You can restrict access to certain functionality (the REST API and profiling endpoints) by prepending a secret string to the paths.
For example, providing the `--admin-prefix=abc123` flag will register the index rebuild path as `/abc123/index/rebuild`. For example, providing the `--admin-prefix=abc123` flag will register the index rebuild path as `/abc123/index/rebuild`.
@ -44,6 +44,21 @@ The restricted paths are:
While this might thwart very basic attacks, the proper solution for most use cases would likely be to add authentication via a reverse proxy. While this might thwart very basic attacks, the proper solution for most use cases would likely be to add authentication via a reverse proxy.
## API
If the `--api` flag is passed, a number of REST endpoints are registered.
The first of these—`/index/`—responds to GET requests with the contents of the index, in JSON format.
The second—`/index/rebuild`—responds to POST requests by rebuilding the index.
This can prove useful when confirming whether the index is generated successfully, or whether a given file is in the index.
The remaining four endpoints respond to GET requests with information about the registered file types:
- `/extensions/available`
- `/extensions/enabled`
- `/types/available`
- `/types/enabled`
## Filtering ## Filtering
You can provide a comma-delimited string of alphanumeric patterns to match via the `include=` query parameter, assuming the `-f|--filter` flag is enabled. You can provide a comma-delimited string of alphanumeric patterns to match via the `include=` query parameter, assuming the `-f|--filter` flag is enabled.
@ -73,15 +88,6 @@ If `--index-file` is set, the index will be loaded from the specified file on st
The index file consists of [zstd](https://facebook.github.io/zstd/)-compressed [gobs](https://pkg.go.dev/encoding/gob). The index file consists of [zstd](https://facebook.github.io/zstd/)-compressed [gobs](https://pkg.go.dev/encoding/gob).
## Info
If the `-i|--info` flag is passed, five additional endpoints are registered.
The first of these—`/index/`—returns the contents of the index, in JSON format.
This can prove useful when confirming whether the index is generated successfully, or whether a given file is in the index.
The remaining four endpoints—`/extensions/available`, `/extensions/enabled`, `/types/available` and `/types/enabled`—return information about the registered file types.
## Refresh ## Refresh
If the `--refresh` flag is passed and a positive-value `refresh=<integer><unit>` query parameter is provided, the page will reload after that interval. If the `--refresh` flag is passed and a positive-value `refresh=<integer><unit>` query parameter is provided, the page will reload after that interval.
@ -143,6 +149,7 @@ Flags:
--admin-prefix string string to prepend to administrative paths --admin-prefix string string to prepend to administrative paths
-a, --all enable all supported file types -a, --all enable all supported file types
--allow-empty allow specifying paths containing no supported files --allow-empty allow specifying paths containing no supported files
--api expose REST API
--audio enable support for audio files --audio enable support for audio files
--binary-prefix use IEC binary prefixes instead of SI decimal prefixes --binary-prefix use IEC binary prefixes instead of SI decimal prefixes
-b, --bind string address to bind to (default "0.0.0.0") -b, --bind string address to bind to (default "0.0.0.0")
@ -158,13 +165,11 @@ Flags:
--flash enable support for shockwave flash files (via ruffle.rs) --flash enable support for shockwave flash files (via ruffle.rs)
--fun add a bit of excitement to your day --fun add a bit of excitement to your day
-h, --help help for roulette -h, --help help for roulette
--ignore skip all directories containing a specified filename --ignore string filename used to indicate directory should be skipped
--ignore-file string filename used to indicate directory should be skipped (default ".roulette-ignore")
--images enable support for image files --images enable support for image files
--index generate index of supported file paths at startup --index generate index of supported file paths at startup
--index-file string path to optional persistent index file --index-file string path to optional persistent index file
--index-interval string interval at which to regenerate index (e.g. "3s" or "1h") --index-interval string interval at which to regenerate index (e.g. "5m" or "1h")
-i, --info expose informational endpoints
--max-file-count int skip directories with file counts above this value (default 2147483647) --max-file-count int skip directories with file counts above this value (default 2147483647)
--min-file-count int skip directories with file counts below this value --min-file-count int skip directories with file counts below this value
-p, --port int port to listen on (default 8080) -p, --port int port to listen on (default 8080)

View File

@ -260,7 +260,7 @@ func walkPath(path string, fileChannel chan<- string, wg1 *sync.WaitGroup, stats
if !node.IsDir() { if !node.IsDir() {
files++ files++
if Ignore && node.Name() == IgnoreFile { if Ignore != "" && node.Name() == Ignore {
skipDir = true skipDir = true
} }
} }

View File

@ -7,12 +7,10 @@ package cmd
import ( import (
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"net/http"
"os" "os"
"sync" "sync"
"time" "time"
"github.com/julienschmidt/httprouter"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
"seedno.de/seednode/roulette/types" "seedno.de/seednode/roulette/types"
) )
@ -227,31 +225,6 @@ func rebuildIndex(args []string, index *fileIndex, formats types.Types, encoder
fileList(args, &filters{}, "", index, formats, encoder, errorChannel) fileList(args, &filters{}, "", index, formats, encoder, errorChannel)
} }
func serveIndexRebuild(args []string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
startTime := time.Now()
rebuildIndex(args, index, formats, encoder, errorChannel)
w.Header().Set("Content-Type", "text/plain;charset=UTF-8")
_, err := w.Write([]byte("Ok\n"))
if err != nil {
errorChannel <- err
return
}
if Verbose {
fmt.Printf("%s | SERVE: Index rebuild requested by %s took %s\n",
startTime.Format(logDate),
realIP(r),
time.Since(startTime).Round(time.Microsecond),
)
}
}
}
func importIndex(args []string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) { func importIndex(args []string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) {
if IndexFile != "" { if IndexFile != "" {
index.Import(IndexFile, errorChannel) index.Import(IndexFile, errorChannel)

View File

@ -13,9 +13,40 @@ import (
"time" "time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/klauspost/compress/zstd"
"seedno.de/seednode/roulette/types" "seedno.de/seednode/roulette/types"
) )
func serveExtensions(formats types.Types, available bool, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
startTime := time.Now()
w.Header().Set("Content-Type", "text/plain;charset=UTF-8")
var extensions string
if available {
extensions = types.SupportedFormats.GetExtensions()
} else {
extensions = formats.GetExtensions()
}
written, err := w.Write([]byte(extensions))
if err != nil {
errorChannel <- err
}
if Verbose {
fmt.Printf("%s | SERVE: Registered extension list (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(written),
realIP(r),
time.Since(startTime).Round(time.Microsecond),
)
}
}
}
func serveIndex(args []string, index *fileIndex, errorChannel chan<- error) httprouter.Handle { func serveIndex(args []string, index *fileIndex, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
startTime := time.Now() startTime := time.Now()
@ -55,29 +86,24 @@ func serveIndex(args []string, index *fileIndex, errorChannel chan<- error) http
} }
} }
func serveExtensions(formats types.Types, available bool, errorChannel chan<- error) httprouter.Handle { func serveIndexRebuild(args []string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
startTime := time.Now() startTime := time.Now()
rebuildIndex(args, index, formats, encoder, errorChannel)
w.Header().Set("Content-Type", "text/plain;charset=UTF-8") w.Header().Set("Content-Type", "text/plain;charset=UTF-8")
var extensions string _, err := w.Write([]byte("Ok\n"))
if available {
extensions = types.SupportedFormats.GetExtensions()
} else {
extensions = formats.GetExtensions()
}
written, err := w.Write([]byte(extensions))
if err != nil { if err != nil {
errorChannel <- err errorChannel <- err
return
} }
if Verbose { if Verbose {
fmt.Printf("%s | SERVE: Registered extension list (%s) to %s in %s\n", fmt.Printf("%s | SERVE: Index rebuild requested by %s took %s\n",
startTime.Format(logDate), startTime.Format(logDate),
humanReadableSize(written),
realIP(r), realIP(r),
time.Since(startTime).Round(time.Microsecond), time.Since(startTime).Round(time.Microsecond),
) )
@ -115,9 +141,10 @@ func serveMediaTypes(formats types.Types, available bool, errorChannel chan<- er
} }
} }
func registerInfoHandlers(mux *httprouter.Router, args []string, index *fileIndex, formats types.Types, errorChannel chan<- error) { func registerAPIHandlers(mux *httprouter.Router, args []string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) {
if Index { if Index {
mux.GET(Prefix+AdminPrefix+"/index", serveIndex(args, index, errorChannel)) mux.GET(Prefix+AdminPrefix+"/index", serveIndex(args, index, errorChannel))
mux.POST(Prefix+AdminPrefix+"/index/rebuild", serveIndexRebuild(args, index, formats, encoder, errorChannel))
} }
mux.GET(Prefix+AdminPrefix+"/extensions/available", serveExtensions(formats, true, errorChannel)) mux.GET(Prefix+AdminPrefix+"/extensions/available", serveExtensions(formats, true, errorChannel))

View File

@ -17,13 +17,14 @@ import (
const ( const (
AllowedCharacters string = `^[A-z0-9.\-_]+$` AllowedCharacters string = `^[A-z0-9.\-_]+$`
ReleaseVersion string = "6.4.3" ReleaseVersion string = "7.0.0"
) )
var ( var (
AdminPrefix string AdminPrefix string
All bool All bool
AllowEmpty bool AllowEmpty bool
API bool
Audio bool Audio bool
BinaryPrefix bool BinaryPrefix bool
Bind string Bind string
@ -38,13 +39,11 @@ var (
Filtering bool Filtering bool
Flash bool Flash bool
Fun bool Fun bool
Ignore bool Ignore string
IgnoreFile string
Images bool Images bool
Index bool Index bool
IndexFile string IndexFile string
IndexInterval string IndexInterval string
Info bool
MaxFileCount int MaxFileCount int
MinFileCount int MinFileCount int
Port int Port int
@ -84,7 +83,7 @@ var (
return ErrInvalidPort return ErrInvalidPort
case Concurrency < 1: case Concurrency < 1:
return ErrInvalidConcurrency return ErrInvalidConcurrency
case Ignore && !regexp.MustCompile(AllowedCharacters).MatchString(IgnoreFile): case Ignore != "" && !regexp.MustCompile(AllowedCharacters).MatchString(Ignore):
return ErrInvalidIgnoreFile return ErrInvalidIgnoreFile
case AdminPrefix != "" && !regexp.MustCompile(AllowedCharacters).MatchString(AdminPrefix): case AdminPrefix != "" && !regexp.MustCompile(AllowedCharacters).MatchString(AdminPrefix):
return ErrInvalidAdminPrefix return ErrInvalidAdminPrefix
@ -118,6 +117,7 @@ func init() {
rootCmd.Flags().StringVar(&AdminPrefix, "admin-prefix", "", "string to prepend to administrative paths") rootCmd.Flags().StringVar(&AdminPrefix, "admin-prefix", "", "string to prepend to administrative paths")
rootCmd.Flags().BoolVarP(&All, "all", "a", false, "enable all supported file types") rootCmd.Flags().BoolVarP(&All, "all", "a", false, "enable all supported file types")
rootCmd.Flags().BoolVar(&AllowEmpty, "allow-empty", false, "allow specifying paths containing no supported files") rootCmd.Flags().BoolVar(&AllowEmpty, "allow-empty", false, "allow specifying paths containing no supported files")
rootCmd.Flags().BoolVar(&API, "api", false, "expose REST API")
rootCmd.Flags().BoolVar(&Audio, "audio", false, "enable support for audio files") rootCmd.Flags().BoolVar(&Audio, "audio", false, "enable support for audio files")
rootCmd.Flags().BoolVar(&BinaryPrefix, "binary-prefix", false, "use IEC binary prefixes instead of SI decimal prefixes") rootCmd.Flags().BoolVar(&BinaryPrefix, "binary-prefix", false, "use IEC binary prefixes instead of SI decimal prefixes")
rootCmd.Flags().StringVarP(&Bind, "bind", "b", "0.0.0.0", "address to bind to") rootCmd.Flags().StringVarP(&Bind, "bind", "b", "0.0.0.0", "address to bind to")
@ -132,13 +132,11 @@ func init() {
rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering") rootCmd.Flags().BoolVarP(&Filtering, "filter", "f", false, "enable filtering")
rootCmd.Flags().BoolVar(&Flash, "flash", false, "enable support for shockwave flash files (via ruffle.rs)") rootCmd.Flags().BoolVar(&Flash, "flash", false, "enable support for shockwave flash files (via ruffle.rs)")
rootCmd.Flags().BoolVar(&Fun, "fun", false, "add a bit of excitement to your day") rootCmd.Flags().BoolVar(&Fun, "fun", false, "add a bit of excitement to your day")
rootCmd.Flags().BoolVar(&Ignore, "ignore", false, "skip all directories containing a specified filename") rootCmd.Flags().StringVar(&Ignore, "ignore", "", "filename used to indicate directory should be skipped")
rootCmd.Flags().StringVar(&IgnoreFile, "ignore-file", ".roulette-ignore", "filename used to indicate directory should be skipped")
rootCmd.Flags().BoolVar(&Images, "images", false, "enable support for image files") rootCmd.Flags().BoolVar(&Images, "images", false, "enable support for image files")
rootCmd.Flags().BoolVar(&Index, "index", false, "generate index of supported file paths at startup") rootCmd.Flags().BoolVar(&Index, "index", false, "generate index of supported file paths at startup")
rootCmd.Flags().StringVar(&IndexFile, "index-file", "", "path to optional persistent index file") rootCmd.Flags().StringVar(&IndexFile, "index-file", "", "path to optional persistent index file")
rootCmd.Flags().StringVar(&IndexInterval, "index-interval", "", "interval at which to regenerate index (e.g. \"5m\" or \"1h\")") rootCmd.Flags().StringVar(&IndexInterval, "index-interval", "", "interval at which to regenerate index (e.g. \"5m\" or \"1h\")")
rootCmd.Flags().BoolVarP(&Info, "info", "i", false, "expose informational endpoints")
rootCmd.Flags().IntVar(&MaxFileCount, "max-file-count", math.MaxInt32, "skip directories with file counts above this value") rootCmd.Flags().IntVar(&MaxFileCount, "max-file-count", math.MaxInt32, "skip directories with file counts above this value")
rootCmd.Flags().IntVar(&MinFileCount, "min-file-count", 0, "skip directories with file counts below this value") rootCmd.Flags().IntVar(&MinFileCount, "min-file-count", 0, "skip directories with file counts below this value")
rootCmd.Flags().IntVarP(&Port, "port", "p", 8080, "port to listen on") rootCmd.Flags().IntVarP(&Port, "port", "p", 8080, "port to listen on")

View File

@ -584,9 +584,11 @@ func ServePage(args []string) error {
quit := make(chan struct{}) quit := make(chan struct{})
defer close(quit) defer close(quit)
if Index { if API {
mux.GET(Prefix+AdminPrefix+"/index/rebuild", serveIndexRebuild(args, index, formats, encoder, errorChannel)) registerAPIHandlers(mux, args, index, formats, encoder, errorChannel)
}
if Index {
importIndex(paths, index, formats, encoder, errorChannel) importIndex(paths, index, formats, encoder, errorChannel)
if IndexInterval != "" { if IndexInterval != "" {
@ -594,10 +596,6 @@ func ServePage(args []string) error {
} }
} }
if Info {
registerInfoHandlers(mux, args, index, formats, errorChannel)
}
if Profile { if Profile {
registerProfileHandlers(mux) registerProfileHandlers(mux)
} }