diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 9d14cfb..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "ansible.python.interpreterPath": "/bin/python"
-}
\ No newline at end of file
diff --git a/cmd/root.go b/cmd/root.go
deleted file mode 100644
index 55db51b..0000000
--- a/cmd/root.go
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
-Copyright © 2024 Evan Fiordeliso
-*/
-package cmd
-
-import (
- "errors"
- "fmt"
- "log/slog"
- "os"
-
- "github.com/dgraph-io/badger/v2"
- "github.com/spf13/cobra"
- "github.com/spf13/viper"
- "go.fifitido.net/ytdl-web/config"
- "go.fifitido.net/ytdl-web/pkg/server"
- "go.fifitido.net/ytdl-web/pkg/utils"
- "go.fifitido.net/ytdl-web/pkg/ytdl"
- "go.fifitido.net/ytdl-web/pkg/ytdl/cache"
-)
-
-var (
- cfgFile string
- cfg *config.Config
-
- rootCmd = &cobra.Command{
- Use: "ytdl-web",
- Short: "A web frontend for yt-dlp",
- Long: `YTDL Web is a web application that grabs the links to videos
-from over a thousand websites using the yt-dlp project under the hood.`,
- RunE: func(cmd *cobra.Command, args []string) error {
- initLogging()
- logger := slog.Default()
-
- db, err := badger.Open(
- badger.
- DefaultOptions(cfg.Cache.DirPath).
- WithLogger(utils.NewBadgerLogger(logger.With("module", "badger"))),
- )
- if err != nil {
- return err
- }
- defer db.Close()
-
- cache := cache.NewDefaultMetadataCache(db)
- yt := ytdl.NewYtdl(cfg, slog.Default(), cache)
- ytdl.SetDefault(yt)
-
- return server.ListenAndServe(
- server.WithListenAddr(viper.GetString("http.listen")),
- server.WithListenPort(viper.GetInt("http.port")),
- server.WithLogger(logger.With("module", "server")),
- )
- },
- }
-)
-
-func Execute() error {
- return rootCmd.Execute()
-}
-
-func init() {
- cobra.OnInitialize(initConfig)
-
- rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $XDG_CONFIG_HOME/ytdl-web/config.yml)")
-
- rootCmd.PersistentFlags().IntP("port", "p", 8080, "port to listen on")
- rootCmd.PersistentFlags().StringP("listen", "l", "", "address to listen on")
- rootCmd.PersistentFlags().StringP("base-path", "b", "", "the base path, used when behind reverse proxy")
- rootCmd.PersistentFlags().StringP("ytdlp-path", "y", "", "the path to the yt-dlp binary, used when it is not in $PATH")
- rootCmd.PersistentFlags().BoolP("cookies-enabled", "C", false, "whether cookies are enabled")
- rootCmd.PersistentFlags().StringP("cookies", "c", "", "the path to the cookies file")
-
- viper.BindPFlag("http.port", rootCmd.PersistentFlags().Lookup("port"))
- viper.BindPFlag("http.listen", rootCmd.PersistentFlags().Lookup("listen"))
- viper.BindPFlag("http.basePath", rootCmd.PersistentFlags().Lookup("base-path"))
- viper.BindPFlag("ytdlp.binaryPath", rootCmd.PersistentFlags().Lookup("ytdlp-path"))
- viper.BindPFlag("cookies.enabled", rootCmd.PersistentFlags().Lookup("cookies-enabled"))
- viper.BindPFlag("cookies.filePath", rootCmd.PersistentFlags().Lookup("cookies"))
-}
-
-func initConfig() {
- var err error
- if cfgFile != "" {
- cfg, err = config.LoadConfig(cfgFile)
- } else {
- cfg, err = config.LoadConfig()
- }
-
- notFound := &viper.ConfigFileNotFoundError{}
- switch {
- case err != nil && !errors.As(err, notFound):
- cobra.CheckErr(err)
- case err != nil && errors.As(err, notFound):
- // The config file is optional, we shouldn't exit when the config is not found
- break
- default:
- fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
- }
-}
-
-func initLogging() {
- if cfg.IsProduction() {
- slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
- AddSource: true,
- Level: slog.LevelInfo,
- })))
- } else {
- slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
- AddSource: true,
- Level: slog.LevelDebug,
- })))
- }
-}
diff --git a/cmd/ytdl-web/main.go b/cmd/ytdl-web/main.go
new file mode 100644
index 0000000..f4e5fe8
--- /dev/null
+++ b/cmd/ytdl-web/main.go
@@ -0,0 +1,98 @@
+/*
+Copyright © 2024 Evan Fiordeliso
+*/
+package main
+
+import (
+ "flag"
+ "log/slog"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/dgraph-io/badger/v2"
+ "github.com/go-chi/chi/v5"
+ "github.com/go-chi/chi/v5/middleware"
+ slogchi "github.com/samber/slog-chi"
+ "go.fifitido.net/ytdl-web/pkg/config"
+ "go.fifitido.net/ytdl-web/pkg/routes"
+ "go.fifitido.net/ytdl-web/pkg/serverctx"
+ "go.fifitido.net/ytdl-web/pkg/utils"
+ "go.fifitido.net/ytdl-web/pkg/ytdl"
+ "go.fifitido.net/ytdl-web/pkg/ytdl/cache"
+)
+
+var (
+ env = config.String("env", "production", "the environment to run")
+ listenAddr = config.String("listen", ":8080", "address to listen on")
+ basePath = config.String("base_path", "/", "the base path, used when behind reverse proxy")
+ ytdlpPath = config.String("ytdlp.binary", "", "path to yt-dlp executable")
+ cacheTTL = config.Duration("cache.ttl", time.Hour, "the TTL for the cache")
+ cacheDir = config.String("cache.dir", "", "the directory to store the cache")
+ cookies = config.Bool("cookies", false, "whether cookies are enabled")
+ cookiesPath = config.String("cookies.path", "", "the path to the cookies file")
+ cookiesBrowser = config.String("cookies.browser", "", "the browser to use for cookies")
+ cookiesBrowserKeyring = config.String("cookies.browser.keyring", "", "the keyring to use for cookies")
+ cookiesBrowserProfile = config.String("cookies.browser.profile", "", "the profile to use for cookies")
+ cookiesBrowserContainer = config.String("cookies.browser.container", "", "the container to use for cookies")
+)
+
+func main() {
+ flag.Parse()
+
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
+ AddSource: true,
+ Level: slog.LevelInfo,
+ }))
+
+ if env() == "development" {
+ logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
+ AddSource: true,
+ Level: slog.LevelDebug,
+ }))
+ }
+
+ slog.SetDefault(logger)
+
+ db, err := badger.Open(
+ badger.
+ DefaultOptions(cacheDir()).
+ WithLogger(utils.NewBadgerLogger(logger.With("module", "badger"))),
+ )
+ if err != nil {
+ os.Exit(1)
+ }
+ defer db.Close()
+
+ cache := cache.NewDefaultMetadataCache(db, cacheTTL())
+ ytdl.SetDefault(
+ ytdl.NewYtdl(&ytdl.Config{
+ BinaryPath: ytdlpPath(),
+ CookiesEnabled: cookies(),
+ CookiesFilePath: cookiesPath(),
+ CookiesBrowser: cookiesBrowser(),
+ CookiesBrowserKeyring: cookiesBrowserKeyring(),
+ CookiesBrowserProfile: cookiesBrowserProfile(),
+ CookiesBrowserContainer: cookiesBrowserContainer(),
+ }, logger, cache),
+ )
+
+ r := chi.NewRouter()
+
+ r.Use(
+ serverctx.Middleware(basePath()),
+ middleware.RequestID,
+ middleware.RealIP,
+ slogchi.New(logger),
+ middleware.Recoverer,
+ )
+
+ r.Mount(basePath(), routes.Router())
+
+ logger.Info("Starting HTTP server", slog.String("listen", listenAddr()))
+
+ if err := http.ListenAndServe(listenAddr(), r); err != nil {
+ logger.Error("failed to serve website", slog.Any("error", err))
+ os.Exit(1)
+ }
+}
diff --git a/config.example.yaml b/config.example.yaml
deleted file mode 100644
index fb9ff06..0000000
--- a/config.example.yaml
+++ /dev/null
@@ -1,47 +0,0 @@
----
-# The server environment
-# For dev environments use Development
-# For prod environments use Production
-# For staging envronments use Staging
-env: Production
-ytdlp:
- # The path to the yt-dlp binary
- # If it is already in your $PATH just yt-dlp will work.
- binaryPath: yt-dlp
-http:
- # The port to listen on
- port: 8080
- # The address to listen on
- # For local only access use 127.0.0.1
- # For public access use 0.0.0.0
- listen: 0.0.0.0
- # The base path of the application, useful for reverse proxies
- basePath: ""
-
- # A list of proxy servers to trust for security purposes
- # Only needed when accessing app behind a proxy
- trustedProxies: []
-cookies:
- # Whether to use cookies when fetching the video metadata
- enabled: false
-
- # The path to the netscape formatted cookies file
- # See: https://www.reddit.com/r/youtubedl/wiki/cookies/ for details.
- filePath: ~/.cookies
-
- # Settings for using cookies from a browser's cookies store
- fromBrowser:
- # The name of the browser to load cookies from.
- # Currently supported browsers are: brave, chrome, chromium, edge,
- # firefox, opera, safari, vivaldi.
- browser: firefox
-
- # The keyring used for decrypting Chromium cookies on Linux
- # Currently supported keyrings are: basictext, gnomekeyring, kwallet
- keyring: basictext
-
- # The profile to load cookies from (Firefox)
- profile: default
-
- # The container to load cookies from (Firefox)
- container: none
diff --git a/config/config.go b/config/config.go
deleted file mode 100644
index 5c4385a..0000000
--- a/config/config.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package config
-
-import (
- "fmt"
- "os"
- "path"
- "strings"
- "time"
-
- "github.com/adrg/xdg"
- "github.com/spf13/viper"
-)
-
-type Config struct {
- Env string `mapstructure:"env"`
- Ytdlp ConfigYtdlp `mapstructure:"ytdlp"`
- HTTP ConfigHTTP `mapstructure:"http"`
- Cache ConfigCache `mapstructure:"cache"`
- Cookies ConfigCookies `mapstructure:"cookies"`
-}
-
-func (c *Config) IsProduction() bool {
- return c.Env == "Production"
-}
-
-func (c *Config) IsDevelopment() bool {
- return c.Env == "Development"
-}
-
-func (c *Config) IsStaging() bool {
- return c.Env == "Staging"
-}
-
-type ConfigYtdlp struct {
- BinaryPath string `mapstructure:"binaryPath"`
-}
-
-type ConfigHTTP struct {
- Port int `mapstructure:"port"`
- Listen string `mapstructure:"listen"`
- BasePath string `mapstructure:"basePath"`
- TrustedProxies []string `mapstructure:"trustedProxies"`
-}
-
-type ConfigCache struct {
- TTL time.Duration `mapstructure:"ttl"`
- DirPath string `mapstructure:"dirPath"`
-}
-
-type ConfigCookies struct {
- Enabled bool `mapstructure:"enabled"`
- FilePath string `mapstructure:"filePath"`
- FromBrowser ConfigCookiesFromBrowser `mapstructure:"fromBrowser"`
-}
-
-type ConfigCookiesFromBrowser struct {
- Browser string `mapstructure:"browser"`
- Keyring string `mapstructure:"keyring"`
- Profile string `mapstructure:"profile"`
- Container string `mapstructure:"container"`
-}
-
-func DefaultConfig() *Config {
- return &Config{
- Env: "Production",
- Ytdlp: ConfigYtdlp{BinaryPath: "yt-dlp"},
- HTTP: ConfigHTTP{
- Port: 8080,
- Listen: "127.0.0.1",
- BasePath: "/",
- },
- Cache: ConfigCache{
- TTL: time.Hour,
- DirPath: "/tmp/ytdl-web",
- },
- Cookies: ConfigCookies{
- Enabled: false,
- FilePath: "./cookies.txt",
- FromBrowser: ConfigCookiesFromBrowser{},
- },
- }
-}
-
-func LoadConfig(paths ...string) (*Config, error) {
- viper.SetEnvPrefix("YTDL")
- viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
- viper.AutomaticEnv()
-
- viper.SetConfigName("config")
- viper.SetConfigType("yaml")
-
- if len(paths) > 0 {
- for _, p := range paths {
- viper.AddConfigPath(path.Dir(p))
- }
- } else {
- envDir := os.Getenv("YTDL_CONFIGDIR")
- if envDir != "" {
- viper.AddConfigPath(envDir)
- }
-
- viper.AddConfigPath(".")
-
- homeDir, err := os.UserHomeDir()
- if err == nil {
- viper.AddConfigPath(homeDir + "/.config/ytdl-web")
- }
-
- viper.AddConfigPath(xdg.ConfigHome + "/ytdl-web")
- for _, dir := range xdg.ConfigDirs {
- viper.AddConfigPath(dir + "/ytdl-web")
- }
- }
-
- if err := viper.ReadInConfig(); err != nil {
- if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
- return nil, err
- }
- }
-
- config := DefaultConfig()
- if err := viper.Unmarshal(config); err != nil {
- return nil, err
- }
-
- fmt.Printf("%#v\n", config)
-
- return config, nil
-}
diff --git a/devenv.nix b/devenv.nix
index c030849..780c365 100644
--- a/devenv.nix
+++ b/devenv.nix
@@ -11,7 +11,12 @@
DOCKER_ORG = "apps";
DOCKER_PLATFORMS = "linux/amd64,linux/arm64";
- YTDL_ENV = "Development";
+ YTDL_WEB_ENV = "development";
+ YTDL_WEB_YTDLP_BINARY = "/usr/bin/yt-dlp";
+ YTDL_WEB_LISTEN = ":8989";
+ YTDL_WEB_CACHE_DIR = config.devenv.runtime + "/cache";
+ YTDL_WEB_COOKIES = "true";
+ YTDL_WEB_COOKIES_BROWSER = "firefox";
};
# https://devenv.sh/packages/
@@ -42,7 +47,7 @@
-X $VERSION_PKG.Build=$(build-id)
-X $VERSION_PKG.BuildDate=$(build-date)
-X $VERSION_PKG.BuiltBy=manual
- " -o $BINARY_OUT .
+ " -o $BINARY_OUT ./cmd/ytdl-web
'';
clean.exec = ''
rm -rf ./dist/ ./out/ ./tmp/
diff --git a/go.mod b/go.mod
index ac0f230..07b513b 100644
--- a/go.mod
+++ b/go.mod
@@ -4,12 +4,9 @@ go 1.25.2
require (
github.com/a-h/templ v0.3.960
- github.com/adrg/xdg v0.5.3
github.com/dgraph-io/badger/v2 v2.2007.4
github.com/go-chi/chi/v5 v5.2.3
github.com/samber/slog-chi v1.17.0
- github.com/spf13/cobra v1.10.1
- github.com/spf13/viper v1.21.0
)
require (
@@ -18,35 +15,13 @@ require (
github.com/dgraph-io/ristretto v0.2.0 // indirect
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/fsnotify/fsnotify v1.9.0 // indirect
- github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
- github.com/hashicorp/hcl v1.0.0 // indirect
- github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.18.1 // indirect
- github.com/kr/text v0.2.0 // indirect
- github.com/magiconair/properties v1.8.10 // indirect
- github.com/mitchellh/mapstructure v1.5.0 // indirect
- github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
- github.com/pelletier/go-toml v1.9.5 // indirect
- github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
- github.com/sagikazarmark/locafero v0.12.0 // indirect
- github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
- github.com/spf13/afero v1.15.0 // indirect
- github.com/spf13/cast v1.10.0 // indirect
- github.com/spf13/jwalterweatherman v1.1.0 // indirect
- github.com/spf13/pflag v1.0.10 // indirect
- github.com/subosito/gotenv v1.6.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
- go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/sys v0.38.0 // indirect
- golang.org/x/text v0.30.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
- gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
- gopkg.in/ini.v1 v1.67.0 // indirect
- gopkg.in/yaml.v2 v2.4.0 // indirect
)
diff --git a/go.sum b/go.sum
index 0b66dad..9b0cfb2 100644
--- a/go.sum
+++ b/go.sum
@@ -3,10 +3,6 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/a-h/templ v0.3.960 h1:trshEpGa8clF5cdI39iY4ZrZG8Z/QixyzEyUnA7feTM=
github.com/a-h/templ v0.3.960/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
-github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
-github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
-github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
-github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@@ -16,198 +12,86 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
-github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
-github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
-github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
-github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
-github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
-github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
-github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
-github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
-github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
-github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
-github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
-github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
-github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
-github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
-github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
-github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
-github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
-github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
-github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
-github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
-github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
-github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/samber/slog-chi v1.17.0 h1:zP66fV4LGF1y1Dg/+uHNY9Uxkmw1YNWaZGws6M6mPCk=
github.com/samber/slog-chi v1.17.0/go.mod h1:a1iIuofF2gS1ii8aXIQhC6TEguLOhOvSM958fY5hToU=
-github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
-github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
-github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
-github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
-github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
-github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
-github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
-github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
-github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
-github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
-github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
-github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
-github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
-github.com/spf13/viper v1.10.0 h1:mXH0UwHS4D2HwWZa75im4xIQynLfblmWV7qcWpfv0yk=
-github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
-github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
-github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
-github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
-github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
-go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
-go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
-go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
-go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
-go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
-golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
-golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
-golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
-gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
-gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/main.go b/main.go
deleted file mode 100644
index 2b2336b..0000000
--- a/main.go
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
-Copyright © 2024 Evan Fiordeliso
-*/
-package main
-
-import (
- "os"
-
- "go.fifitido.net/ytdl-web/cmd"
-)
-
-func main() {
- if err := cmd.Execute(); err != nil {
- os.Exit(1)
- }
-}
diff --git a/nix/devenv.nix b/nix/devenv.nix
deleted file mode 100644
index 1ce61dc..0000000
--- a/nix/devenv.nix
+++ /dev/null
@@ -1,107 +0,0 @@
-{ pkgs, ... }:
-
-{
- # https://devenv.sh/basics/
- env.NAME = "ytdl-web";
- env.BINARY_OUT = "./out/ytdl-web";
- env.VERSION = "v1.2.3";
- env.VERSION_PKG = "go.fifitido.net/ytdl-web/version";
- env.DOCKER_REGISTRY = "git.fifitido.net";
- env.DOCKER_ORG = "apps";
- env.DOCKER_PLATFORMS = "linux/amd64,linux/arm64";
-
- env.YTDL_ENV = "Development";
-
- # https://devenv.sh/packages/
- packages = with pkgs; [
- git
- air
- goreleaser
- docker
- docker-compose
- buildkit
- docker-buildx
- yt-dlp
- cobra-cli
- ];
-
- # https://devenv.sh/scripts/
- scripts.deps.exec = "go mod download";
- scripts.tidy.exec = "go mod tidy";
- scripts.check.exec = "goreleaser check";
-
- scripts.build-id.exec = "git rev-parse --short HEAD";
- scripts.build-date.exec = "date -Iseconds";
-
- scripts.build.exec = ''
- go build -ldflags="
- -X $VERSION_PKG.Version=$VERSION
- -X $VERSION_PKG.Build=$(build-id)
- -X $VERSION_PKG.BuildDate=$(build-date)
- -X $VERSION_PKG.BuiltBy=manual
- " -o $BINARY_OUT .
- '';
- scripts.clean.exec = ''
- rm -rf ./dist/ ./out/ ./tmp/
- go clean
- '';
- scripts.release.exec = ''
- clean
- goreleaser release
- docker-build-release
- '';
- scripts.lint.exec = "trunk check";
- scripts.fmt.exec = "trunk fmt";
-
- scripts.docker-image.exec = "echo $DOCKER_REGISTRY/$DOCKER_ORG/$NAME";
-
- scripts.docker-init.exec = ''
- docker buildx create \
- --name $NAME \
- --platform $DOCKER_PLATFORMS
- '';
-
- scripts.docker-login.exec = "docker login $DOCKER_REGISTRY";
-
- scripts.docker-build.exec = ''
- platform=''${1:-linux}
- output=''${2:-type=docker}
-
- docker-init
- PROGRESS_NO_TRUNC=1 docker buildx build . \
- --tag $(docker-image):$VERSION \
- --tag $(docker-image):latest \
- --platform $platform \
- --builder $NAME \
- --build-arg VERSION=$VERSION \
- --build-arg BUILD=$(build-id) \
- --build-arg BUILD_DATE=$(build-date) \
- --build-arg BUILT_BY="Devenv Script" \
- --progress plain \
- --output $output
- '';
-
- scripts.docker-build-release.exec = "docker-build $DOCKER_PLATFORMS type=image,push=true";
-
- enterShell = ''
- echo "Welcome to the $NAME development environment."
- echo -n "Golang version: $(go version | cut -d ' ' -f 3), "
- echo -n "Goreleaser version: $(goreleaser --version | tail -n 9 | head -n 1 | cut -c 16-), "
- echo -n "Docker CLI version: $(docker version -f {{.Client.Version}}), "
- echo -n "Buildx version: $(docker buildx version | cut -d ' ' -f 2), "
- echo "yt-dlp version: $(yt-dlp --version)"
- '';
-
- # https://devenv.sh/languages/
- languages.go.enable = true;
-
- # https://devenv.sh/pre-commit-hooks/
- pre-commit.hooks.staticcheck.enable = true;
- pre-commit.hooks.hadolint.enable = true;
- pre-commit.hooks.yamllint.enable = true;
-
- # https://devenv.sh/processes/
- processes.web.exec = "air";
-
- # See full reference at https://devenv.sh/reference/options/
-}
diff --git a/nix/package.nix b/nix/package.nix
index 830c882..770a137 100644
--- a/nix/package.nix
+++ b/nix/package.nix
@@ -22,15 +22,6 @@ buildGoModule rec {
"-X $VERSION_PKG.BuiltBy=nix"
];
- nativeBuildInputs = [ installShellFiles ];
-
- postInstall = ''
- installShellCompletion --cmd ${pname} \
- --zsh <($out/bin/${pname} completion zsh) \
- --bash <($out/bin/${pname} completion bash) \
- --fish <($out/bin/${pname} completion fish)
- '';
-
meta = with lib; {
description = "Yet another yt-dlp web frontend written in Go.";
homepage = "https://git.fifitido.net/apps/ytdl-web";
diff --git a/pkg/config/var.go b/pkg/config/var.go
new file mode 100644
index 0000000..e70e5f7
--- /dev/null
+++ b/pkg/config/var.go
@@ -0,0 +1,104 @@
+package config
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+)
+
+func getEnvKey(key string) string {
+ return fmt.Sprintf("YTDL_WEB_%s", strings.ReplaceAll(strings.ToUpper(key), ".", "_"))
+}
+
+func String(key string, defaultValue string, usage string) func() string {
+ flagValue := flag.String(key, defaultValue, usage)
+ return func() string {
+ if flagValue != nil && *flagValue != defaultValue {
+ return *flagValue
+ }
+
+ envValue, ok := os.LookupEnv(getEnvKey(key))
+ if ok {
+ return envValue
+ }
+
+ return defaultValue
+ }
+}
+
+func Int(key string, defaultValue int, usage string) func() int {
+ flagValue := flag.Int(key, defaultValue, usage)
+ return func() int {
+ if flagValue != nil && *flagValue != defaultValue {
+ return *flagValue
+ }
+
+ envValue, ok := os.LookupEnv(getEnvKey(key))
+ if ok {
+ value, err := strconv.Atoi(envValue)
+ if err == nil {
+ return value
+ }
+ }
+
+ return defaultValue
+ }
+}
+
+func Bool(key string, defaultValue bool, usage string) func() bool {
+ flagValue := flag.Bool(key, defaultValue, usage)
+ return func() bool {
+ if flagValue != nil && *flagValue != defaultValue {
+ return *flagValue
+ }
+
+ envValue, ok := os.LookupEnv(getEnvKey(key))
+ if ok {
+ value, err := strconv.ParseBool(envValue)
+ if err == nil {
+ return value
+ }
+ }
+
+ return defaultValue
+ }
+}
+
+func StringSlice(key string, defaultValue []string, usage string) func() []string {
+ defaultString := strings.Join(defaultValue, ",")
+ flagValue := flag.String(key, defaultString, usage)
+ return func() []string {
+ if flagValue != nil && *flagValue != defaultString {
+ return strings.Split(*flagValue, ",")
+ }
+
+ envValue, ok := os.LookupEnv(getEnvKey(key))
+ if ok {
+ return strings.Split(envValue, ",")
+ }
+
+ return defaultValue
+ }
+}
+
+func Duration(key string, defaultValue time.Duration, usage string) func() time.Duration {
+ flagValue := flag.Duration(key, defaultValue, usage)
+ return func() time.Duration {
+ if flagValue != nil && *flagValue != defaultValue {
+ return *flagValue
+ }
+
+ envValue, ok := os.LookupEnv(getEnvKey(key))
+ if ok {
+ value, err := time.ParseDuration(envValue)
+ if err == nil {
+ return value
+ }
+ }
+
+ return defaultValue
+ }
+}
diff --git a/pkg/server/routes.go b/pkg/routes/routes.go
similarity index 97%
rename from pkg/server/routes.go
rename to pkg/routes/routes.go
index 5b9318c..94cccbf 100644
--- a/pkg/server/routes.go
+++ b/pkg/routes/routes.go
@@ -1,4 +1,4 @@
-package server
+package routes
import (
"fmt"
@@ -16,13 +16,15 @@ import (
"go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
)
-func Routes(r chi.Router) {
+func Router() chi.Router {
+ r := chi.NewRouter()
r.Get("/", home)
r.Route("/download", func(r chi.Router) {
r.Get("/", download)
r.Head("/proxy", proxyDownload)
r.Get("/proxy", proxyDownload)
})
+ return r
}
func renderPage(w http.ResponseWriter, r *http.Request, component templ.Component) {
diff --git a/pkg/server/options.go b/pkg/server/options.go
deleted file mode 100644
index 5cb1c51..0000000
--- a/pkg/server/options.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package server
-
-import "log/slog"
-
-type Options struct {
- ListenAddr string
- ListenPort int
- Logger *slog.Logger
-}
-
-func DefaultOptions() *Options {
- return &Options{
- ListenAddr: "127.0.0.1",
- ListenPort: 8080,
- Logger: slog.Default(),
- }
-}
-
-type Option func(*Options)
-
-func WithListenAddr(addr string) Option {
- return func(o *Options) {
- o.ListenAddr = addr
- }
-}
-
-func WithListenPort(port int) Option {
- return func(o *Options) {
- o.ListenPort = port
- }
-}
-
-func WithLogger(logger *slog.Logger) Option {
- return func(o *Options) {
- o.Logger = logger
- }
-}
diff --git a/pkg/server/server.go b/pkg/server/server.go
deleted file mode 100644
index f7bb343..0000000
--- a/pkg/server/server.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package server
-
-import (
- "fmt"
- "log/slog"
- "net/http"
-
- "github.com/go-chi/chi/v5"
- "github.com/go-chi/chi/v5/middleware"
- slogchi "github.com/samber/slog-chi"
- "go.fifitido.net/ytdl-web/pkg/serverctx"
-)
-
-func ListenAndServe(options ...Option) error {
- opts := DefaultOptions()
- for _, opt := range options {
- opt(opts)
- }
-
- r := chi.NewRouter()
-
- r.Use(
- serverctx.Middleware,
- middleware.RequestID,
- middleware.RealIP,
- slogchi.New(opts.Logger),
- middleware.Recoverer,
- )
-
- r.Group(Routes)
-
- listenAddr := fmt.Sprintf("%s:%d", opts.ListenAddr, opts.ListenPort)
- opts.Logger.Info("Starting HTTP server", slog.String("addr", opts.ListenAddr), slog.Int("port", opts.ListenPort))
- return http.ListenAndServe(listenAddr, r)
-}
diff --git a/pkg/serverctx/context.go b/pkg/serverctx/context.go
index 902c743..cb625b5 100644
--- a/pkg/serverctx/context.go
+++ b/pkg/serverctx/context.go
@@ -8,18 +8,30 @@ import (
type contextKey string
const (
- isHTTPS contextKey = "isHTTPS"
+ isHTTPSKey contextKey = "isHTTPS"
+ basePathKey contextKey = "basePath"
)
-func Middleware(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- isHttps := r.URL.Scheme == "https" || r.Header.Get("X-Forwarded-Proto") == "https"
- ctx := context.WithValue(r.Context(), isHTTPS, isHttps)
- next.ServeHTTP(w, r.WithContext(ctx))
- })
+func Middleware(basePath string) func(next http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ isHttps := r.URL.Scheme == "https" || r.Header.Get("X-Forwarded-Proto") == "https"
+ ctx := context.WithValue(r.Context(), isHTTPSKey, isHttps)
+ ctx = context.WithValue(ctx, basePathKey, basePath)
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+ }
}
func IsHTTPS(ctx context.Context) bool {
- isHttps, ok := ctx.Value(isHTTPS).(bool)
+ isHttps, ok := ctx.Value(isHTTPSKey).(bool)
return ok && isHttps
}
+
+func BasePath(ctx context.Context) string {
+ basePath, ok := ctx.Value(basePathKey).(string)
+ if !ok {
+ return ""
+ }
+ return basePath
+}
diff --git a/pkg/views/downloads.templ b/pkg/views/downloads.templ
index 2c5297e..095f339 100644
--- a/pkg/views/downloads.templ
+++ b/pkg/views/downloads.templ
@@ -31,7 +31,7 @@ templ videoFormatView(vm *DownloadsViewModel, vidIndex int, format metadata.Form
Download (proxied)
@@ -106,8 +106,8 @@ templ Downloads(vm *DownloadsViewModel) {
{ *vm.Meta.Title }
{ vm.Url }
here.
-