Compare commits

..

5 Commits

11 changed files with 165 additions and 333 deletions

View File

@ -37,8 +37,7 @@ The restricted paths are:
- `/debug/pprof/trace`
- `/extensions/available`
- `/extensions/enabled`
- `/index/html`
- `/index/json`
- `/index/`
- `/index/rebuild`
- `/types/available`
- `/types/enabled`
@ -64,7 +63,7 @@ Both filtering parameters ignore the file extension and full path; they only com
If the `--ignore` flag is passed, any directory containing a file named `.roulette-ignore` (configurable with `--ignore-file`) will be skipped during the scanning stage.
## Indexing
If the `-i|--indexing` flag is passed, all specified paths will be indexed on start.
If the `-i|--index` flag is passed, all specified paths will be indexed on start.
This will slightly increase the delay before the application begins responding to requests, but should significantly speed up subsequent requests.
@ -81,11 +80,9 @@ Supported formats are `none`, `zlib`, and `zstd`.
Optionally, `--compression-fast` can be used to use the fastest instead of the best compression mode.
## Info
If the `-i|--info` flag is passed, six additional endpoints are registered.
If the `-i|--info` flag is passed, five additional endpoints are registered.
The first of these—`/index/html` and `/index/json`—return the contents of the index, in HTML and JSON formats respectively.
If `--page-length` is also set, these can be viewed in paginated form by appending a page number, e.g. `/index/html/5` for the fifth page.
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.
@ -166,7 +163,6 @@ Flags:
-f, --filter enable filtering
--flash enable support for shockwave flash files (via ruffle.rs)
--fun add a bit of excitement to your day
--handlers display registered handlers (for debugging)
-h, --help help for roulette
--ignore skip all directories containing a specified filename
--ignore-file string filename used to indicate directory to be skipped (default ".roulette-ignore")
@ -176,7 +172,6 @@ Flags:
-i, --info expose informational endpoints
--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
--page-length int pagination length for info pages
-p, --port int port to listen on (default 8080)
--prefix string root path for http handlers (for reverse proxying) (default "/")
--profile register net/http/pprof handlers

View File

@ -5,7 +5,6 @@ Copyright © 2024 Seednode <seednode@seedno.de>
package cmd
import (
"bytes"
"embed"
"net/http"
"strconv"
@ -36,12 +35,7 @@ func serveFavicons(errorChannel chan<- error) httprouter.Handle {
return
}
err = w.Header().Write(bytes.NewBufferString("Content-Length: " + strconv.Itoa(len(data))))
if err != nil {
errorChannel <- err
return
}
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
_, err = w.Write(data)
if err != nil {

View File

@ -25,10 +25,6 @@ import (
"seedno.de/seednode/roulette/types"
)
var (
filename = regexp.MustCompile(`(.+?)([0-9]*)(\..+)`)
)
type scanStats struct {
filesMatched chan int
filesSkipped chan int
@ -79,14 +75,14 @@ func kill(path string, index *fileIndex) error {
return nil
}
func newFile(list []string, sortOrder string, formats types.Types) (string, error) {
func newFile(list []string, sortOrder string, filename *regexp.Regexp, formats types.Types) (string, error) {
path, err := pickFile(list)
if err != nil {
return "", err
}
if sortOrder == "asc" || sortOrder == "desc" {
splitPath, err := split(path)
splitPath, err := split(path, filename)
if err != nil {
return "", err
}
@ -125,8 +121,8 @@ func newFile(list []string, sortOrder string, formats types.Types) (string, erro
return path, nil
}
func nextFile(filePath, sortOrder string, formats types.Types) (string, error) {
splitPath, err := split(filePath)
func nextFile(filePath, sortOrder string, filename *regexp.Regexp, formats types.Types) (string, error) {
splitPath, err := split(filePath, filename)
if err != nil {
return "", err
}
@ -543,31 +539,39 @@ func validatePaths(args []string, formats types.Types) ([]string, error) {
switch {
case pathMatches && hasSupportedFiles:
fmt.Printf("%s | PATHS: Added %s\n",
time.Now().Format(logDate),
args[i],
)
if Verbose {
fmt.Printf("%s | PATHS: Added %s\n",
time.Now().Format(logDate),
args[i],
)
}
paths = append(paths, path)
case !pathMatches && hasSupportedFiles:
fmt.Printf("%s | PATHS: Added %s [resolved to %s]\n",
time.Now().Format(logDate),
args[i],
path,
)
if Verbose {
fmt.Printf("%s | PATHS: Added %s [resolved to %s]\n",
time.Now().Format(logDate),
args[i],
path,
)
}
paths = append(paths, path)
case pathMatches && !hasSupportedFiles:
fmt.Printf("%s | PATHS: Skipped %s (No supported files found)\n",
time.Now().Format(logDate),
args[i],
)
if Verbose {
fmt.Printf("%s | PATHS: Skipped %s (No supported files found)\n",
time.Now().Format(logDate),
args[i],
)
}
case !pathMatches && !hasSupportedFiles:
fmt.Printf("%s | PATHS: Skipped %s [resolved to %s] (No supported files found)\n",
time.Now().Format(logDate),
args[i],
path,
)
if Verbose {
fmt.Printf("%s | PATHS: Skipped %s [resolved to %s] (No supported files found)\n",
time.Now().Format(logDate),
args[i],
path,
)
}
}
}

View File

@ -9,7 +9,6 @@ import (
"fmt"
"net/http"
"os"
"runtime"
"sync"
"time"
@ -213,8 +212,6 @@ func serveIndexRebuild(args []string, index *fileIndex, formats types.Types, enc
time.Since(startTime).Round(time.Microsecond),
)
}
runtime.GC()
}
}

View File

@ -7,194 +7,28 @@ package cmd
import (
"encoding/json"
"fmt"
"io"
"net/http"
"sort"
"strconv"
"strings"
"time"
"github.com/julienschmidt/httprouter"
"github.com/yosssi/gohtml"
"seedno.de/seednode/roulette/types"
)
func paginateIndex(page int, fileCount int, ending bool) string {
var firstPage int = 1
var lastPage int
if fileCount%PageLength == 0 {
lastPage = fileCount / PageLength
} else {
lastPage = (fileCount / PageLength) + 1
}
var prevStatus, nextStatus string = "", ""
if page <= 1 {
prevStatus = " disabled"
}
if page >= lastPage {
nextStatus = " disabled"
}
prevPage := page - 1
if prevPage < 1 {
prevPage = 1
}
nextPage := page + 1
if nextPage > lastPage {
nextPage = fileCount / PageLength
}
var html strings.Builder
if ending {
html.WriteString("<tr><td style=\"border-bottom:none;\">")
} else {
html.WriteString("<tr><td>")
}
html.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '%s%s/index/html/%d';\">First</button>",
Prefix,
AdminPrefix,
firstPage))
html.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '%s%s/index/html/%d';\"%s>Prev</button>",
Prefix,
AdminPrefix,
prevPage,
prevStatus))
html.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '%s%s/index/html/%d';\"%s>Next</button>",
Prefix,
AdminPrefix,
nextPage,
nextStatus))
html.WriteString(fmt.Sprintf("<button onclick=\"window.location.href = '%s%s/index/html/%d';\">Last</button>",
Prefix,
AdminPrefix,
lastPage))
html.WriteString("</td></tr>\n")
return html.String()
}
func serveIndexHtml(args []string, index *fileIndex, shouldPaginate bool) httprouter.Handle {
func serveIndex(args []string, index *fileIndex, 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/html")
indexDump := index.List()
fileCount := len(indexDump)
var startIndex, stopIndex int
page, err := strconv.Atoi(p.ByName("page"))
if err != nil || page <= 0 {
startIndex = 0
stopIndex = fileCount
} else {
startIndex = ((page - 1) * PageLength)
stopIndex = (startIndex + PageLength)
}
if startIndex > (fileCount - 1) {
indexDump = []string{}
}
if stopIndex > fileCount {
stopIndex = fileCount
}
sort.SliceStable(indexDump, func(p, q int) bool {
return strings.ToLower(indexDump[p]) < strings.ToLower(indexDump[q])
})
var htmlBody strings.Builder
htmlBody.WriteString(`<!DOCTYPE html><html lang="en"><head>`)
htmlBody.WriteString(faviconHtml)
htmlBody.WriteString(`<style>a{text-decoration:none;height:100%;width:100%;color:inherit;cursor:pointer}`)
htmlBody.WriteString(`table,td,tr{border:none;}td{border-bottom:1px solid black;}td{white-space:nowrap;padding:.5em}</style>`)
htmlBody.WriteString(fmt.Sprintf("<title>Index contains %d files</title></head><body><table>", fileCount))
if shouldPaginate && !DisableButtons {
htmlBody.WriteString(paginateIndex(page, fileCount, false))
}
if len(indexDump) > 0 {
for _, v := range indexDump[startIndex:stopIndex] {
var shouldSort = ""
if Sorting {
shouldSort = "?sort=asc"
}
htmlBody.WriteString(fmt.Sprintf("<tr><td><a href=\"%s%s%s%s\">%s</a></td></tr>\n", Prefix, mediaPrefix, v, shouldSort, v))
}
}
if shouldPaginate && !DisableButtons {
htmlBody.WriteString(paginateIndex(page, fileCount, true))
}
htmlBody.WriteString(`</table></body></html>`)
written, err := io.WriteString(w, gohtml.Format(htmlBody.String()))
if err != nil {
return
}
if Verbose {
fmt.Printf("%s | SERVE: HTML index page (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(written),
realIP(r),
time.Since(startTime).Round(time.Microsecond),
)
}
}
}
func serveIndexJson(args []string, index *fileIndex, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
startTime := time.Now()
w.Header().Set("Content-Type", "application/json")
indexedFiles := index.List()
fileCount := len(indexedFiles)
sort.SliceStable(indexedFiles, func(p, q int) bool {
return strings.ToLower(indexedFiles[p]) < strings.ToLower(indexedFiles[q])
})
var startIndex, stopIndex int
page, err := strconv.Atoi(p.ByName("page"))
if err != nil || page <= 0 {
startIndex = 0
stopIndex = fileCount
} else {
startIndex = ((page - 1) * PageLength)
stopIndex = (startIndex + PageLength)
}
if startIndex > (fileCount - 1) {
indexedFiles = []string{}
}
if stopIndex > fileCount {
stopIndex = fileCount
}
response, err := json.MarshalIndent(indexedFiles[startIndex:stopIndex], "", " ")
response, err := json.MarshalIndent(indexDump, "", " ")
if err != nil {
errorChannel <- err
@ -203,6 +37,8 @@ func serveIndexJson(args []string, index *fileIndex, errorChannel chan<- error)
return
}
response = append(response, []byte("\n")...)
written, err := w.Write(response)
if err != nil {
errorChannel <- err
@ -219,35 +55,21 @@ func serveIndexJson(args []string, index *fileIndex, errorChannel chan<- error)
}
}
func serveAvailableExtensions(errorChannel chan<- error) httprouter.Handle {
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")
written, err := w.Write([]byte(types.SupportedFormats.GetExtensions()))
if err != nil {
errorChannel <- err
var extensions string
if available {
extensions = types.SupportedFormats.GetExtensions()
} else {
extensions = formats.GetExtensions()
}
if Verbose {
fmt.Printf("%s | SERVE: Available extension list (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(written),
realIP(r),
time.Since(startTime).Round(time.Microsecond),
)
}
}
}
func serveEnabledExtensions(formats types.Types, 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")
written, err := w.Write([]byte(formats.GetExtensions()))
written, err := w.Write([]byte(extensions))
if err != nil {
errorChannel <- err
}
@ -263,13 +85,21 @@ func serveEnabledExtensions(formats types.Types, errorChannel chan<- error) http
}
}
func serveAvailableMediaTypes(errorChannel chan<- error) httprouter.Handle {
func serveMediaTypes(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")
written, err := w.Write([]byte(types.SupportedFormats.GetMediaTypes()))
var mediaTypes string
if available {
mediaTypes = types.SupportedFormats.GetMediaTypes()
} else {
mediaTypes = formats.GetMediaTypes()
}
written, err := w.Write([]byte(mediaTypes))
if err != nil {
errorChannel <- err
}
@ -285,43 +115,13 @@ func serveAvailableMediaTypes(errorChannel chan<- error) httprouter.Handle {
}
}
func serveEnabledMediaTypes(formats types.Types, 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")
written, err := w.Write([]byte(formats.GetMediaTypes()))
if err != nil {
errorChannel <- err
}
if Verbose {
fmt.Printf("%s | SERVE: Registered media type list (%s) to %s in %s\n",
startTime.Format(logDate),
humanReadableSize(written),
realIP(r),
time.Since(startTime).Round(time.Microsecond),
)
}
}
}
func registerInfoHandlers(mux *httprouter.Router, args []string, index *fileIndex, formats types.Types, errorChannel chan<- error) {
if Index {
registerHandler(mux, Prefix+AdminPrefix+"/index/html", serveIndexHtml(args, index, false))
if PageLength != 0 {
registerHandler(mux, Prefix+AdminPrefix+"/index/html/:page", serveIndexHtml(args, index, true))
}
registerHandler(mux, Prefix+AdminPrefix+"/index/json", serveIndexJson(args, index, errorChannel))
if PageLength != 0 {
registerHandler(mux, Prefix+AdminPrefix+"/index/json/:page", serveIndexJson(args, index, errorChannel))
}
mux.GET(Prefix+AdminPrefix+"/index", serveIndex(args, index, errorChannel))
}
registerHandler(mux, Prefix+AdminPrefix+"/extensions/available", serveAvailableExtensions(errorChannel))
registerHandler(mux, Prefix+AdminPrefix+"/extensions/enabled", serveEnabledExtensions(formats, errorChannel))
registerHandler(mux, Prefix+AdminPrefix+"/types/available", serveAvailableMediaTypes(errorChannel))
registerHandler(mux, Prefix+AdminPrefix+"/types/enabled", serveEnabledMediaTypes(formats, errorChannel))
mux.GET(Prefix+AdminPrefix+"/extensions/available", serveExtensions(formats, true, errorChannel))
mux.GET(Prefix+AdminPrefix+"/extensions/enabled", serveExtensions(formats, false, errorChannel))
mux.GET(Prefix+AdminPrefix+"/types/available", serveMediaTypes(formats, true, errorChannel))
mux.GET(Prefix+AdminPrefix+"/types/enabled", serveMediaTypes(formats, false, errorChannel))
}

View File

@ -17,7 +17,7 @@ import (
const (
AllowedCharacters string = `^[A-z0-9.\-_]+$`
ReleaseVersion string = "5.4.3"
ReleaseVersion string = "6.1.1"
)
var (
@ -38,7 +38,6 @@ var (
Filtering bool
Flash bool
Fun bool
Handlers bool
Ignore bool
IgnoreFile string
Images bool
@ -47,12 +46,10 @@ var (
Info bool
MaxFileCount int
MinFileCount int
PageLength int
Port int
Prefix string
Profile bool
Recursive bool
Redact bool
Refresh bool
Russian bool
Sorting bool
@ -126,7 +123,7 @@ func init() {
rootCmd.Flags().BoolVar(&CaseSensitive, "case-sensitive", false, "use case-sensitive matching for filters")
rootCmd.Flags().BoolVar(&Code, "code", false, "enable support for source code files")
rootCmd.Flags().StringVar(&CodeTheme, "code-theme", "solarized-dark256", "theme for source code syntax highlighting")
rootCmd.Flags().IntVar(&Concurrency, "concurrency", 10240, "maximum concurrency for scan threads")
rootCmd.Flags().IntVar(&Concurrency, "concurrency", 1024, "maximum concurrency for scan threads")
rootCmd.Flags().BoolVarP(&Debug, "debug", "d", false, "display even more verbose logs")
rootCmd.Flags().BoolVar(&DisableButtons, "disable-buttons", false, "disable first/prev/next/last buttons")
rootCmd.Flags().BoolVar(&ExitOnError, "exit-on-error", false, "shut down webserver on error, instead of just printing error")
@ -134,21 +131,18 @@ func init() {
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(&Fun, "fun", false, "add a bit of excitement to your day")
rootCmd.Flags().BoolVar(&Handlers, "handlers", false, "display registered handlers (for debugging)")
rootCmd.Flags().BoolVar(&Ignore, "ignore", false, "skip all directories containing a specified filename")
rootCmd.Flags().StringVar(&IgnoreFile, "ignore-file", ".roulette-ignore", "filename used to indicate directory to 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(&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().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(&MinFileCount, "min-file-count", 0, "skip directories with file counts below this value")
rootCmd.Flags().IntVar(&PageLength, "page-length", 0, "pagination length for info pages")
rootCmd.Flags().IntVarP(&Port, "port", "p", 8080, "port to listen on")
rootCmd.Flags().StringVar(&Prefix, "prefix", "/", "root path for http handlers (for reverse proxying)")
rootCmd.Flags().BoolVar(&Profile, "profile", false, "register net/http/pprof handlers")
rootCmd.Flags().BoolVarP(&Recursive, "recursive", "r", false, "recurse into subdirectories")
rootCmd.Flags().BoolVar(&Redact, "redact", false, "redact admin prefix in log output")
rootCmd.Flags().BoolVar(&Refresh, "refresh", false, "enable automatic page refresh via query parameter")
rootCmd.Flags().BoolVar(&Russian, "russian", false, "remove selected images after serving")
rootCmd.Flags().BoolVarP(&Sorting, "sort", "s", false, "enable sorting")

View File

@ -6,6 +6,7 @@ package cmd
import (
"fmt"
"regexp"
"sort"
"strings"
@ -38,7 +39,7 @@ func (splitPath *splitPath) decrement() string {
return fmt.Sprintf("%0*d", len(splitPath.number), asInt-1)
}
func split(path string) (*splitPath, error) {
func split(path string, filename *regexp.Regexp) (*splitPath, error) {
split := filename.FindAllStringSubmatch(path, -1)
if len(split) < 1 || len(split[0]) < 3 {
@ -54,8 +55,8 @@ func split(path string) (*splitPath, error) {
return p, nil
}
func getRange(path string, index *fileIndex) (string, string, error) {
splitPath, err := split(path)
func getRange(path string, index *fileIndex, filename *regexp.Regexp) (string, string, error) {
splitPath, err := split(path, filename)
if err != nil {
return "", "", err
}
@ -70,7 +71,7 @@ func getRange(path string, index *fileIndex) (string, string, error) {
Loop:
for _, val := range list {
splitVal, err := split(val)
splitVal, err := split(val, filename)
if err != nil {
return "", "", err
}
@ -94,8 +95,8 @@ func pathUrlEscape(path string) string {
return strings.Replace(path, `'`, `%27`, -1)
}
func paginateSorted(path, first, last, queryParams string, formats types.Types) (string, error) {
split, err := split(path)
func paginate(path, first, last, queryParams string, filename *regexp.Regexp, formats types.Types) (string, error) {
split, err := split(path, filename)
if err != nil {
return "", err
}

View File

@ -5,7 +5,6 @@ Copyright © 2024 Seednode <seednode@seedno.de>
package cmd
import (
"bytes"
"errors"
"fmt"
"io"
@ -13,6 +12,7 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
@ -32,8 +32,6 @@ import (
"seedno.de/seednode/roulette/types/video"
)
var ()
const (
logDate string = `2006-01-02T15:04:05.000-07:00`
sourcePrefix string = `/source`
@ -171,7 +169,7 @@ func serveStaticFile(paths []string, index *fileIndex, errorChannel chan<- error
}
}
func serveRoot(paths []string, index *fileIndex, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) httprouter.Handle {
func serveRoot(paths []string, index *fileIndex, filename *regexp.Regexp, formats types.Types, encoder *zstd.Encoder, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
refererUri, err := stripQueryParams(refererToUri(r.Referer()))
if err != nil {
@ -196,7 +194,7 @@ func serveRoot(paths []string, index *fileIndex, formats types.Types, encoder *z
var path string
if refererUri != "" {
path, err = nextFile(strippedRefererUri, sortOrder, formats)
path, err = nextFile(strippedRefererUri, sortOrder, filename, formats)
if err != nil {
errorChannel <- err
@ -220,7 +218,7 @@ func serveRoot(paths []string, index *fileIndex, formats types.Types, encoder *z
break loop
}
path, err = newFile(list, sortOrder, formats)
path, err = newFile(list, sortOrder, filename, formats)
switch {
case path == "":
noFiles(w, r)
@ -251,7 +249,7 @@ func serveRoot(paths []string, index *fileIndex, formats types.Types, encoder *z
}
}
func serveMedia(paths []string, index *fileIndex, formats types.Types, errorChannel chan<- error) httprouter.Handle {
func serveMedia(paths []string, index *fileIndex, filename *regexp.Regexp, formats types.Types, errorChannel chan<- error) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
startTime := time.Now()
@ -314,7 +312,7 @@ func serveMedia(paths []string, index *fileIndex, formats types.Types, errorChan
return
}
mediaType := format.MediaType(path)
mediaType := format.MediaType(filepath.Ext(path))
fileUri := Prefix + generateFileUri(path)
@ -347,7 +345,7 @@ func serveMedia(paths []string, index *fileIndex, formats types.Types, errorChan
var first, last string
if Index && sortOrder != "" {
first, last, err = getRange(path, index)
first, last, err = getRange(path, index, filename)
if err != nil {
errorChannel <- err
@ -358,7 +356,7 @@ func serveMedia(paths []string, index *fileIndex, formats types.Types, errorChan
}
if Index && !DisableButtons && sortOrder != "" {
paginate, err := paginateSorted(path, first, last, queryParams, formats)
paginated, err := paginate(path, first, last, queryParams, filename, formats)
if err != nil {
errorChannel <- err
@ -367,7 +365,7 @@ func serveMedia(paths []string, index *fileIndex, formats types.Types, errorChan
return
}
htmlBody.WriteString(paginate)
htmlBody.WriteString(paginated)
}
if refreshInterval != "0ms" {
@ -388,7 +386,7 @@ func serveMedia(paths []string, index *fileIndex, formats types.Types, errorChan
formattedPage := gohtml.Format(htmlBody.String())
written, err := io.WriteString(w, formattedPage)
written, err := io.WriteString(w, formattedPage+"\n")
if err != nil {
errorChannel <- err
@ -426,12 +424,7 @@ func serveVersion(errorChannel chan<- error) httprouter.Handle {
data := []byte(fmt.Sprintf("roulette v%s\n", ReleaseVersion))
err := w.Header().Write(bytes.NewBufferString("Content-Length: " + strconv.Itoa(len(data))))
if err != nil {
errorChannel <- err
return
}
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
written, err := w.Write(data)
if err != nil {
@ -451,21 +444,6 @@ func serveVersion(errorChannel chan<- error) httprouter.Handle {
}
}
func registerHandler(mux *httprouter.Router, path string, handle httprouter.Handle) {
mux.GET(path, handle)
if Redact && AdminPrefix != "" {
path = strings.ReplaceAll(path, AdminPrefix, "/<admin_prefix>")
}
if Handlers {
fmt.Printf("%s | SERVE: Registered handler for %s\n",
time.Now().Format(logDate),
path,
)
}
}
func redirectRoot() httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
newUrl := fmt.Sprintf("http://%s%s",
@ -539,10 +517,6 @@ func ServePage(args []string) error {
return ErrNoMediaFound
}
if !strings.HasSuffix(Prefix, "/") {
Prefix = Prefix + "/"
}
listenHost := net.JoinHostPort(Bind, strconv.Itoa(Port))
index := &fileIndex{
@ -584,26 +558,32 @@ func ServePage(args []string) error {
return err
}
registerHandler(mux, Prefix, serveRoot(paths, index, formats, encoder, errorChannel))
filename := regexp.MustCompile(`(.+?)([0-9]*)(\..+)`)
if !strings.HasSuffix(Prefix, "/") {
Prefix = Prefix + "/"
}
mux.GET(Prefix, serveRoot(paths, index, filename, formats, encoder, errorChannel))
Prefix = strings.TrimSuffix(Prefix, "/")
if Prefix != "" {
registerHandler(mux, "/", redirectRoot())
mux.GET("/", redirectRoot())
}
registerHandler(mux, Prefix+"/favicons/*favicon", serveFavicons(errorChannel))
mux.GET(Prefix+"/favicons/*favicon", serveFavicons(errorChannel))
registerHandler(mux, Prefix+"/favicon.ico", serveFavicons(errorChannel))
mux.GET(Prefix+"/favicon.ico", serveFavicons(errorChannel))
registerHandler(mux, Prefix+mediaPrefix+"/*media", serveMedia(paths, index, formats, errorChannel))
mux.GET(Prefix+mediaPrefix+"/*media", serveMedia(paths, index, filename, formats, errorChannel))
registerHandler(mux, Prefix+sourcePrefix+"/*static", serveStaticFile(paths, index, errorChannel))
mux.GET(Prefix+sourcePrefix+"/*static", serveStaticFile(paths, index, errorChannel))
registerHandler(mux, Prefix+"/version", serveVersion(errorChannel))
mux.GET(Prefix+"/version", serveVersion(errorChannel))
if Index {
registerHandler(mux, Prefix+AdminPrefix+"/index/rebuild", serveIndexRebuild(args, index, formats, encoder, errorChannel))
mux.GET(Prefix+AdminPrefix+"/index/rebuild", serveIndexRebuild(args, index, formats, encoder, errorChannel))
importIndex(paths, index, formats, encoder, errorChannel)
}

48
docker/Dockerfile.debug Normal file
View File

@ -0,0 +1,48 @@
# set app name
ARG app=roulette
# create build stage
ARG TAG
FROM --platform=$BUILDPLATFORM golang:$TAG AS build
ARG app
# install dependencies
RUN apk add --update-cache git upx
# clone
RUN git clone https://git.seedno.de/seednode/$app /src/$app
# build and compress the binary
WORKDIR /src/$app
ARG TARGETOS TARGETARCH
RUN CGO_ENABLED=0 \
GOOS=$TARGETOS \
GOARCH=$TARGETARCH \
go build -trimpath -ldflags "-s -w" -o $app \
&& upx --best --lzma $app \
&& chmod 500 $app
# set up final stage
FROM --platform=$BUILDPLATFORM alpine:latest
ARG app
# copy in user info
COPY --chown=root:root --chmod=0400 passwd /etc/passwd
# run as nonroot
USER root
# copy in binary
COPY --from=build --chown=root:root --chmod=0005 /src/$app/$app /$app
# copy in time zone info
COPY --from=build --chown=root:root --chmod=0004 /usr/local/go/lib/time/zoneinfo.zip /
# load time zone info
ENV ZONEINFO=/zoneinfo.zip
# listen on an unprivileged port
EXPOSE 8080
# run application
ENTRYPOINT ["/bin/ash"]

View File

@ -37,3 +37,19 @@ docker buildx build --platform "${platforms}" \
$(if [ "${LATEST}" == "yes" ]; then echo "-t ${registry}/${image_name}:latest"; fi) \
-f Dockerfile . \
--push
# copy debug image to local image repository
docker buildx build \
--build-arg TAG="${tag}" \
-t "${registry}/${image_name}:${image_version}-debug" \
$(if [ "${LATEST}" == "yes" ]; then echo "-t ${registry}/${image_name}:debug"; fi) \
-f Dockerfile.debug . \
--load
# push debug image to remote registry
docker buildx build --platform "${platforms}" \
--build-arg TAG="${tag}" \
-t "${registry}/${image_name}:${image_version}-debug" \
$(if [ "${LATEST}" == "yes" ]; then echo "-t ${registry}/${image_name}:debug"; fi) \
-f Dockerfile.debug . \
--push

View File

@ -99,8 +99,11 @@ func (t Types) GetMediaTypes() string {
for _, j := range t {
extensions := j.Extensions()
for _, v := range extensions {
mediaTypes = append(mediaTypes, v)
if v != "" {
mediaTypes = append(mediaTypes, v)
}
}
}