Compare commits

...

3 Commits
v1.2.0 ... main

13 changed files with 1061 additions and 56 deletions

12
.envrc
View File

@ -1,3 +1,11 @@
source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0="
if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
fi
use devenv
nix_direnv_watch_file nix/devenv.nix
nix_direnv_watch_file devenv.lock
nix_direnv_watch_file devenv.yaml
if ! use flake . --impure
then
echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
fi

View File

@ -36,7 +36,7 @@ You can configure the application using environment variables
| YTDL_COOKIES_FROMBROWSER_BROWSER | The name of the browser to load cookies from. (if specified, it disables YTDL_COOKIES_FILEPATH) | ` ` | `brave`, `chrome`, `chromium`, `edge`, `firefox`, `opera`, `safari`, `vivaldi` |
| YTDL_COOKIES_FROMBROWSER_KEYRING | The name of the keyring for decrypting cookies for the chromium browser on linux | ` ` | `basictext`, `gnomekeyring`, `kwallet` |
| YTDL_COOKIES_FROMBROWSER_PROFILE | The browser profile to load cookies from | ` ` | |
| YTDL_COOKIES_FROMBROWSER_CONTAINER | The container name (if firefox) top load the cookies from | ` ` | |
| YTDL_COOKIES_FROMBROWSER_CONTAINER | The container name (if firefox) to load the cookies from | ` ` | |
## Building from source

85
cmd/completion.go Normal file
View File

@ -0,0 +1,85 @@
/*
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
// completionCmd represents the completion command
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish|powershell]",
Short: "Generate completion script",
Long: fmt.Sprintf(`To load completions:
Bash:
$ source <(%[1]s completion bash)
# To load completions for each session, execute once:
# Linux:
$ %[1]s completion bash > /etc/bash_completion.d/%[1]s
# macOS:
$ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
Zsh:
# If shell completion is not already enabled in your environment,
# you will need to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
$ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
# You will need to start a new shell for this setup to take effect.
fish:
$ %[1]s completion fish | source
# To load completions for each session, execute once:
$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
PowerShell:
PS> %[1]s completion powershell | Out-String | Invoke-Expression
# To load completions for every new session, run:
PS> %[1]s completion powershell > %[1]s.ps1
# and source this file from your PowerShell profile.
`, rootCmd.Root().Name()),
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Run: func(cmd *cobra.Command, args []string) {
switch args[0] {
case "bash":
cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
cmd.Root().GenFishCompletion(os.Stdout, true)
case "powershell":
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
}
},
}
func init() {
rootCmd.AddCommand(completionCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// completionCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View File

@ -4,17 +4,13 @@ Copyright © 2023 Evan Fiordeliso <evan.fiordeliso@gmail.com>
package cmd
import (
"errors"
"fmt"
"os"
"github.com/dgraph-io/badger/v2"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.fifitido.net/ytdl-web/app/controllers"
"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"
"golang.org/x/exp/slog"
)
@ -25,47 +21,13 @@ var (
rootCmd = &cobra.Command{
Use: "ytdl-web",
Short: "A web frontend for yt-dlp",
Long: `YTDL Web
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 {
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)
ytdl := ytdl.NewYtdl(cfg, slog.Default(), cache)
s := server.New(
server.
DefaultOptions().
WithListenAddr(viper.GetString("http.listen")).
WithListenPort(viper.GetInt("http.port")).
WithLogger(logger.With("module", "server")),
)
s.MountController("/", controllers.NewHomeController(ytdl))
s.MountController("/download", controllers.NewDownloadController(ytdl))
return s.ListenAndServe()
},
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.`,
}
)
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
func Execute() error {
return rootCmd.Execute()
}
func init() {
@ -98,14 +60,16 @@ func initConfig() {
cfg, err = config.LoadConfig()
}
if err != nil {
slog.Error("Error loading configuration", slog.String("error", err.Error()))
os.Exit(1)
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())
}
initLogging()
slog.Info("Configuration loaded")
}
func initLogging() {

68
cmd/serve.go Normal file
View File

@ -0,0 +1,68 @@
/*
Copyright © 2023 Evan Fiordeliso <evan.fiordeliso@gmail.com>
*/
package cmd
import (
"github.com/dgraph-io/badger/v2"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.fifitido.net/ytdl-web/app/controllers"
"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"
"golang.org/x/exp/slog"
)
var (
serveCmd = &cobra.Command{
Use: "serve",
Short: "Serve the ytdl-web application",
Long: `Serve the ytdl-web application`,
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)
ytdl := ytdl.NewYtdl(cfg, slog.Default(), cache)
s := server.New(
server.
DefaultOptions().
WithListenAddr(viper.GetString("http.listen")).
WithListenPort(viper.GetInt("http.port")).
WithLogger(logger.With("module", "server")),
)
s.MountController("/", controllers.NewHomeController(ytdl))
s.MountController("/download", controllers.NewDownloadController(ytdl))
return s.ListenAndServe()
},
}
)
func init() {
rootCmd.AddCommand(serveCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// completionCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

548
flake.lock Normal file
View File

@ -0,0 +1,548 @@
{
"nodes": {
"cachix": {
"inputs": {
"devenv": "devenv_2",
"flake-compat": [
"devenv",
"flake-compat"
],
"nixpkgs": [
"devenv",
"nixpkgs"
],
"pre-commit-hooks": [
"devenv",
"pre-commit-hooks"
]
},
"locked": {
"lastModified": 1712055811,
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
"owner": "cachix",
"repo": "cachix",
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "cachix",
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix",
"flake-compat": "flake-compat_2",
"nix": "nix_2",
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1713195025,
"narHash": "sha256-TIUCx0Vlu9ImxTtRyQUFf5RWvTT4GxCTKRSiMFZdZuw=",
"owner": "cachix",
"repo": "devenv",
"rev": "01e01a3b4d991983dda055d10dcf93e86e1e1f8a",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"devenv_2": {
"inputs": {
"flake-compat": [
"devenv",
"cachix",
"flake-compat"
],
"nix": "nix",
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix",
"pre-commit-hooks": [
"devenv",
"cachix",
"pre-commit-hooks"
]
},
"locked": {
"lastModified": 1708704632,
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
"owner": "cachix",
"repo": "devenv",
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "python-rewrite",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"id": "flake-parts",
"type": "indirect"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"devenv",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"mk-shell-bin": {
"locked": {
"lastModified": 1677004959,
"narHash": "sha256-/uEkr1UkJrh11vD02aqufCxtbF5YnhRTIKlx5kyvf+I=",
"owner": "rrbutani",
"repo": "nix-mk-shell-bin",
"rev": "ff5d8bd4d68a347be5042e2f16caee391cd75887",
"type": "github"
},
"original": {
"owner": "rrbutani",
"repo": "nix-mk-shell-bin",
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": [
"devenv",
"cachix",
"devenv",
"nixpkgs"
],
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1708577783,
"narHash": "sha256-92xq7eXlxIT5zFNccLpjiP7sdQqQI30Gyui2p/PfKZM=",
"owner": "domenkozar",
"repo": "nix",
"rev": "ecd0af0c1f56de32cbad14daa1d82a132bf298f8",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.21",
"repo": "nix",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"devenv",
"cachix",
"devenv",
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1688870561,
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nix2container": {
"inputs": {
"flake-utils": "flake-utils_3",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1712990762,
"narHash": "sha256-hO9W3w7NcnYeX8u8cleHiSpK2YJo7ecarFTUlbybl7k=",
"owner": "nlewo",
"repo": "nix2container",
"rev": "20aad300c925639d5d6cbe30013c8357ce9f2a2e",
"type": "github"
},
"original": {
"owner": "nlewo",
"repo": "nix2container",
"type": "github"
}
},
"nix_2": {
"inputs": {
"flake-compat": [
"devenv",
"flake-compat"
],
"nixpkgs": [
"devenv",
"nixpkgs"
],
"nixpkgs-regression": "nixpkgs-regression_2"
},
"locked": {
"lastModified": 1712911606,
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
"owner": "domenkozar",
"repo": "nix",
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.21",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1692808169,
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1711703276,
"narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d8fe5e6c92d0d190646fb9f1056741a229980089",
"type": "github"
},
"original": {
"dir": "lib",
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-regression_2": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1710695816,
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "614b4613980a522ba49f0d194531beddbb7220d3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1710796454,
"narHash": "sha256-lQlICw60RhH8sHTDD/tJiiJrlAfNn8FDI9c+7G2F0SE=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "06fb0f1c643aee3ae6838dda3b37ef0abc3c763b",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1710796454,
"narHash": "sha256-lQlICw60RhH8sHTDD/tJiiJrlAfNn8FDI9c+7G2F0SE=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "06fb0f1c643aee3ae6838dda3b37ef0abc3c763b",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"devenv",
"cachix",
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1692876271,
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": [
"devenv",
"flake-compat"
],
"flake-utils": "flake-utils_2",
"gitignore": "gitignore",
"nixpkgs": [
"devenv",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1712897695,
"narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"flake-parts": "flake-parts",
"mk-shell-bin": "mk-shell-bin",
"nix2container": "nix2container",
"nixpkgs": "nixpkgs_3"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

56
flake.nix Normal file
View File

@ -0,0 +1,56 @@
{
description = "Description for the project";
inputs = {
nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling";
devenv.url = "github:cachix/devenv";
nix2container.url = "github:nlewo/nix2container";
nix2container.inputs.nixpkgs.follows = "nixpkgs";
mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin";
};
nixConfig = {
extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=";
extra-substituters = "https://devenv.cachix.org";
};
outputs = inputs@{ self, flake-parts, ... }:
let
version = "1.2.0";
rev = if (self ? rev) then self.rev else self.dirtyRev;
in
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
inputs.devenv.flakeModule
];
systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
perSystem = { config, self', inputs', pkgs, system, ... }: {
# Per-system attributes can be defined here. The self' and inputs'
# module parameters provide easy access to attributes of the same
# system.
# needed for devenv up
packages.devenv-up = self'.devShells.default.config.procfileScript;
packages.default = pkgs.callPackage ./nix/package.nix { inherit rev version; };
devenv.shells.default = {
name = "ytdl-web";
imports = [
./nix/devenv.nix
];
};
};
flake = {
# The usual flake attributes can be defined here, including system-
# agnostic ones like nixosModule and system-enumerating ones, although
# those are more easily expressed in perSystem.
nixosModules.default = import ./nix/module.nix;
};
};
}

10
main.go
View File

@ -3,8 +3,14 @@ Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package main
import "go.fifitido.net/ytdl-web/cmd"
import (
"os"
"go.fifitido.net/ytdl-web/cmd"
)
func main() {
cmd.Execute()
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}

View File

@ -22,6 +22,7 @@
buildkit
docker-buildx
yt-dlp
cobra-cli
];
# https://devenv.sh/scripts/

229
nix/module.nix Normal file
View File

@ -0,0 +1,229 @@
{ config, lib, pkgs, ... }:
let
inherit (lib.options) mkOption mkEnableOption mkPackageOption;
inherit (lib.modules) mkIf;
inherit (lib.types) str enum int path submodule nullOr bool;
cfg = config.services.ytdl-web;
in
{
options.services.ytdl-web = {
enable = mkEnableOption "ytdl-web";
package = mkPackageOption pkgs "ytdl-web" { };
user = mkOption {
type = str;
default = "ytdl-web";
description = ''
The user account ytdl-web will run under.
'';
};
group = mkOption {
type = str;
default = "ytdl-web";
description = ''
The group ytdl-web will run under.
'';
};
appEnvironment = mkOption {
type = enum [ "Development" "Staging" "Production" ];
default = "Production";
description = ''
The application environment mode.
'';
};
ytdlPackage = mkPackageOption pkgs "yt-dlp" { };
port = mkOption {
type = int;
default = 8080;
description = ''
The tcp port for the web server to listen on.
'';
};
listen = mkOption {
type = str;
default = "0.0.0.0";
example = "127.0.0.1";
description = ''
The address for the web server to listen on.
'';
};
openFirewall = mkOption {
type = bool;
default = false;
example = literalExpression "true";
description = ''
Open ports in the firewall for the ytdl-web server.
'';
};
basePath = mkOption {
type = str;
default = "/";
example = "/ytdl-web";
description = ''
The base path that the web application is hosted under.
Useful for reverse-proxies that proxy the app with a path prefix.
'';
};
cacheTTL = mkOption {
type = str;
default = "1h";
example = "2m";
description = ''
How long to keep cached metadata for.
A duration string is a possibly signed sequence of decimal numbers,
each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or "2h45m".
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
'';
};
cacheDir = mkOption {
type = path;
default = "/var/cache/ytdl-web";
example = "/tmp/ytdl-web";
description = ''
The directory containing the ytdl metadata cache.
'';
};
cookies = mkOption {
type = submodule {
options = {
enable = mkEnableOption "ytdl cookies";
file = mkOption {
type = nullOr path;
default = null;
example = "/etc/ytdl-web/cookies.txt";
description = ''
The file that contains the netscape formatted cookies.
'';
};
fromBrowser = mkOption {
description = ''
Settings for obtaining cookies from a local browser's cookie storage.
'';
type = submodule {
options = {
browser = mkOption {
type = nullOr (enum [ "brave" "chrome" "chromium" "edge" "firefox" "opera" "safari" "vivaldi" ]);
default = null;
description = ''
The name of the browser to load cookies from.
'';
};
keyring = mkOption {
type = nullOr (enum [ "basictext" "gnomekeyring" "kwallet" ]);
default = null;
description = ''
The name of the keyring to use to decrypt cookies when using the chromium browser.
'';
};
profile = mkOption {
type = nullOr str;
default = null;
description = ''
The name of the browser profile to load the cookies from.
'';
};
container = mkOption {
type = nullOr str;
default = null;
description = ''
The container name to load the cookies from when using the firefox browser.
'';
};
};
};
};
};
};
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = cfg.cookies.file != null && cfg.cookies.fromBrowser.browser != null;
message = "The `services.ytdl-web.cookies.file` and `services.ytdl-web.cookies.fromBrowser.browser` options are mutually exclusive.";
}
{
assertion = cfg.cookies.fromBrowser.browser != null &&
!(builtins.elem cfg.cookies.fromBrowser.browser [ "brave" "chrome" "chromium" "edge" "opera" "vivaldi" ]) &&
cfg.cookies.fromBrowser.keyring != null;
message = "The `services.ytdl-web.cookies.fromBrowser.keyring` only functions with a chromium-based browser.";
}
{
assertion = cfg.cookies.fromBrowser.browser != null &&
cfg.cookies.fromBrowser.browser != "firefox" &&
cfg.cookies.fromBrowser.container != null;
message = "The `services.ytdl-web.cookies.fromBrowser.container` only functions with the firefox browser.";
}
];
systemd.services.ytdl-web = {
description = "ytdl-web";
after = [ "networking.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
YTDL_ENV = cfg.appEnvironment;
YTDL_BINARY_PATH = toString cfg.ytdlPackage;
YTDL_HTTP_PORT = toString cfg.port;
YTDL_HTTP_LISTEN = cfg.listen;
YTDL_HTTP_BASEPATH = cfg.basePath;
YTDL_CACHE_TTL = cfg.cacheTTL;
YTDL_CACHE_DIRPATH = cfg.cacheDir;
YTDL_COOKIES_ENABLED = if cfg.cookies.enable then "true" else "false";
YTDL_COOKIES_FILEPATH = cfg.cookies.file;
YTDL_COOKIES_FROMBROWSER_BROWSER = cfg.cookies.fromBrowser.browser;
YTDL_COOKIES_FROMBROWSER_KEYRING = cfg.cookies.fromBrowser.keyring;
YTDL_COOKIES_FROMBROWSER_PROFILE = cfg.cookies.fromBrowser.profile;
YTDL_COOKIES_FROMBROWSER_CONTAINER = cfg.cookies.fromBrowser.container;
};
serviceConfig = {
Type = "simple";
User = cfg.user;
Group = cfg.group;
ExecStart = "${lib.getExe cfg.package} serve";
Restart = "on-failure";
};
};
systemd.tmpfiles.rules = [
"d ${cfg.cacheDir} 0770 ${cfg.user} ${cfg.group} -"
];
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
};
users = {
users = mkIf (cfg.user == "ytdl-web") {
ytdl-web = {
isSystemUser = true;
group = cfg.group;
};
};
groups = mkIf (cfg.group == "ytdl-web") {
ytdl-web = { };
};
};
};
}

40
nix/package.nix Normal file
View File

@ -0,0 +1,40 @@
{ buildGoModule
, installShellFiles
, lib
, rev
, version
, ...
}:
buildGoModule rec {
pname = "ytdl-web";
inherit version;
src = ./..;
vendorHash = "sha256-Rqh5tGcSey53e0Ln3u5agvOwRJ6/I1eUpzRylwtjhQo=";
ldflags = [
"-s"
"-w"
"-X $VERSION_PKG.Version=${version}"
"-X $VERSION_PKG.Build=${rev}"
"-X $VERSION_PKG.BuildDate=1970-01-01T0:00:00+0000"
"-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";
license = licenses.gpl3Only;
mainProgram = "ytdl-web";
};
}