* add Nix support

* fix formatter output

* mention Nix in README

* fix common import

* fix frontend old version import

* clarified flake pkgs order

* rm old dataDir option

* comment typo

* fix password assertion

* rm old User/Group logic

* rewrite assertion boolean expr

* General flake touchup

- Rewrite `callPackage` exprs to be more readable
- Add pre-commit support for devShell
- Add direnv support

* add simple test

* use correct test func
This commit is contained in:
Emanuel Johnson Godin
2024-08-28 20:58:46 +02:00
committed by GitHub
parent 8eebd424c8
commit 650f1cad92
12 changed files with 551 additions and 4 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

4
.gitignore vendored
View File

@@ -1,3 +1,7 @@
.pre-commit-config.yaml
.direnv/
result/
result
dist
.pnpm-debug.log
node_modules

View File

@@ -235,6 +235,10 @@ It is **planned** to also expose a **gRPC** server.
For more information open an issue on GitHub and I will provide more info ASAP.
## Nix
This repo adds support for Nix(OS) in various ways through a `flake-parts` flake.
For more info, please refer to the [official documentation](https://nixos.org/learn/).
## What yt-dlp-webui is not
`yt-dlp-webui` isn't your ordinary website where to download stuff from the internet, so don't try asking for links of where this is hosted. It's a self hosted platform for a Linux NAS.

View File

@@ -1,4 +0,0 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
nativeBuildInputs = with pkgs.buildPackages; [ yt-dlp nodejs_22 yarn-berry go ];
}

149
flake.lock generated Normal file
View File

@@ -0,0 +1,149 @@
{
"nodes": {
"flake-compat": {
"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": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks-nix",
"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"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1723637854,
"narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1722555339,
"narHash": "sha256-uFf2QeW7eAHlYXuDktm9c25OxOyCoUOQmh5SZ9amE5Q=",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1720386169,
"narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1719082008,
"narHash": "sha256-jHJSUH619zBQ6WdC21fFAlDxHErKVDJ5fpN0Hgx4sjs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9693852a2070b398ee123a329e68f0dab5526681",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks-nix": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": "nixpkgs_2",
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1723803910,
"narHash": "sha256-yezvUuFiEnCFbGuwj/bQcqg7RykIEqudOy/RBrId0pc=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "bfef0ada09e2c8ac55bbcd0831bd0c9d42e651ba",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs",
"pre-commit-hooks-nix": "pre-commit-hooks-nix"
}
}
},
"root": "root",
"version": 7
}

51
flake.nix Normal file
View File

@@ -0,0 +1,51 @@
{
description = "A terrible web ui for yt-dlp. Designed to be self-hosted.";
inputs = {
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
pre-commit-hooks-nix.url = "github:cachix/pre-commit-hooks.nix";
};
outputs = inputs@{ self, flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
inputs.pre-commit-hooks-nix.flakeModule
];
systems = [
"x86_64-linux"
];
perSystem = { config, self', pkgs, ... }: {
packages = {
yt-dlp-web-ui-frontend = pkgs.callPackage ./nix/frontend.nix { };
default = pkgs.callPackage ./nix/server.nix {
inherit (self'.packages) yt-dlp-web-ui-frontend;
};
};
checks = import ./nix/tests { inherit self pkgs; };
pre-commit = {
check.enable = true;
settings = {
hooks = {
${self'.formatter.pname}.enable = true;
deadnix.enable = true;
nil.enable = true;
statix.enable = true;
};
};
};
devShells.default = pkgs.callPackage ./nix/devShell.nix {
inputsFrom = [ config.pre-commit.devShell ];
};
formatter = pkgs.nixpkgs-fmt;
};
flake = {
nixosModules.default = import ./nix/module.nix self.packages;
};
};
}

9
nix/common.nix Normal file
View File

@@ -0,0 +1,9 @@
{ lib }: {
version = "v3.1.2";
meta = {
description = "A terrible web ui for yt-dlp. Designed to be self-hosted.";
homepage = "https://github.com/marcopeocchi/yt-dlp-web-ui";
license = lib.licenses.mpl20;
};
}

9
nix/devShell.nix Normal file
View File

@@ -0,0 +1,9 @@
{ inputsFrom ? [ ], mkShell, yt-dlp, nodejs, go }:
mkShell {
inherit inputsFrom;
packages = [
yt-dlp
nodejs
go
];
}

37
nix/frontend.nix Normal file
View File

@@ -0,0 +1,37 @@
{ lib
, stdenv
, nodejs
, pnpm
}:
let common = import ./common.nix { inherit lib; }; in
stdenv.mkDerivation (finalAttrs: {
pname = "yt-dlp-web-ui-frontend";
inherit (common) version;
src = lib.fileset.toSource {
root = ../frontend;
fileset = ../frontend;
};
buildPhase = ''
npm run build
'';
installPhase = ''
mkdir -p $out/dist
cp -r dist/* $out/dist
'';
nativeBuildInputs = [
nodejs
pnpm.configHook
];
pnpmDeps = pnpm.fetchDeps {
inherit (finalAttrs) pname version src;
hash = "sha256-NvXNDXkuoJ4vGeQA3bOhhc+KLBfke593qK0edcvzWTo=";
};
inherit (common) meta;
})

215
nix/module.nix Normal file
View File

@@ -0,0 +1,215 @@
packages: { config, lib, pkgs, ... }:
let
cfg = config.services.yt-dlp-web-ui;
inherit (pkgs.stdenv.hostPlatform) system;
pkg = packages.${system}.default;
in
{
/*
Some notes on the module design:
- Usually, you don't map out all of the options like this in attrsets,
but due to the software's nonstandard "config file overrides CLI" behavior,
we don't want to expose a config file catchall, and as such don't use '-conf'.
- Notably, '-driver' is missing as a configuration option.
This should instead be customized with idiomatic Nix, overriding 'cfg.package' with
the desired yt-dlp package.
- The systemd service has been sandboxed as much as possible. This restricts configuration of
data and logs dir. If you really need a custom data and logs dir, use BindPaths (man systemd.exec)
*/
options.services.yt-dlp-web-ui = {
enable = lib.mkEnableOption "yt-dlp-web-ui";
package = lib.mkOption {
type = lib.types.package;
default = pkg;
defaultText = lib.literalMD "`packages.default` from the yt-dlp-web-ui flake.";
description = ''
The yt-dlp-web-ui package to use.
'';
};
user = lib.mkOption {
type = lib.types.str;
default = "yt-dlp-web-ui";
description = lib.mdDoc ''
User under which yt-dlp-web-ui runs.
'';
};
group = lib.mkOption {
type = lib.types.str;
default = "yt-dlp-web-ui";
description = lib.mdDoc ''
Group under which yt-dlp-web-ui runs.
'';
};
openFirewall = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Whether to open the TCP port in the firewall.
'';
};
host = lib.mkOption {
default = "0.0.0.0";
type = lib.types.str;
description = lib.mdDoc ''
Host where yt-dlp-web-ui will listen at.
'';
};
port = lib.mkOption {
default = 3033;
type = lib.types.port;
description = lib.mdDoc ''
Port where yt-dlp-web-ui will listen at.
'';
};
downloadDir = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
The directory where yt-dlp-web-ui stores downloads.
'';
};
queueSize = lib.mkOption {
default = 2;
type = lib.types.ints.unsigned; # >= 0
description = lib.mdDoc ''
Queue size (concurrent downloads).
'';
};
logging = lib.mkEnableOption "logging";
rpcAuth = lib.mkOption {
description = lib.mdDoc ''
RPC Authentication settings.
'';
default = { };
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption "RPC authentication";
user = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc ''
Username required for auth.
'';
};
passwordFile = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = lib.mdDoc ''
Path to the file containing the password required for auth.
'';
};
insecurePasswordText = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = lib.mdDoc ''
Raw password required for auth.
It's strongly recommended to use 'passwordFile' instead of this option.
**Don't use this option unless you know what you're doing!**.
It writes the password to the world-readable Nix store, which is a big security risk.
More info: https://wiki.nixos.org/wiki/Comparison_of_secret_managing_schemes
'';
};
};
};
};
};
config = lib.mkIf cfg.enable {
assertions = [
(lib.mkIf cfg.rpcAuth.enable {
assertion = lib.xor (cfg.rpcAuth.passwordFile == null) (cfg.rpcAuth.insecurePasswordText == null);
message = ''
RPC Auth is enabled for yt-dlp-web-ui! Exactly one RPC auth password source must be set!
Tip: You should set 'services.yt-dlp-web-ui.rpcAuth.passwordfile'!
'';
})
];
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
users.users = lib.mkIf (cfg.user == "yt-dlp-web-ui") {
yt-dlp-web-ui = {
inherit (cfg) group;
isSystemUser = true;
};
};
users.groups = lib.mkIf (cfg.group == "yt-dlp-web-ui") { yt-dlp-web-ui = { }; };
systemd.services.yt-dlp-web-ui = {
description = "yt-dlp-web-ui system service";
after = [ "network.target" ];
path = [ cfg.package pkgs.tree ];
wantedBy = [ "multi-user.target" ];
serviceConfig =
rec {
ExecStart =
let
password =
if cfg.rpcAuth.passwordFile == null
then cfg.rpcAuth.insecurePasswordText
else "$(cat ${cfg.rpcAuth.passwordFile})";
args = [
"-host ${cfg.host}"
"-port ${builtins.toString cfg.port}"
''-out "${cfg.downloadDir}"''
"-qs ${builtins.toString cfg.queueSize}"
] ++ (lib.optionals cfg.logging [
"-fl"
''-lf "/var/log/${LogsDirectory}/yt-dlp-web-ui.log"''
]) ++ (lib.optionals cfg.rpcAuth.enable [
"-auth"
"-user ${cfg.rpcAuth.user}"
"-pass ${password}"
]);
in
"${lib.getExe cfg.package} ${lib.concatStringsSep " " args}";
User = cfg.user;
Group = cfg.group;
ProtectSystem = "strict";
ProtectHome = "read-only";
StateDirectory = "yt-dlp-web-ui";
WorkingDirectory = "/var/lib/${StateDirectory}"; # equivalent to the dir above
LogsDirectory = "yt-dlp-web-ui";
ReadWritePaths = [
cfg.downloadDir
];
BindReadOnlyPaths = [
builtins.storeDir
# required for youtube DNS lookup
"${config.environment.etc."ssl/certs/ca-certificates.crt".source}:/etc/ssl/certs/ca-certificates.crt"
] ++ lib.optionals (cfg.rpcAuth.enable && cfg.rpcAuth.passwordFile != null) [
cfg.rpcAuth.passwordFile
];
CapabilityBoundingSet = "";
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
PrivateDevices = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" ];
RestrictRealtime = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
ProtectHostname = true;
};
};
};
}

52
nix/server.nix Normal file
View File

@@ -0,0 +1,52 @@
{ yt-dlp-web-ui-frontend, buildGoModule, lib, makeWrapper, yt-dlp, ... }:
let
fs = lib.fileset;
common = import ./common.nix { inherit lib; };
in
buildGoModule {
pname = "yt-dlp-web-ui";
inherit (common) version;
src = fs.toSource rec {
root = ../.;
fileset = fs.difference root (fs.unions [
### LIST OF FILES TO IGNORE ###
# frontend (this is included by the frontend.nix drv instead)
../frontend
# documentation
../examples
# docker
../Dockerfile
../docker-compose.yml
# nix
./devShell.nix
../.envrc
./tests
# make
../Makefile # this derivation does not use the project Makefile
# repo commons
../.github
../README.md
../LICENSE.md
../.gitignore
../.vscode
]);
};
# https://github.com/golang/go/issues/44507
preBuild = ''
cp -r ${yt-dlp-web-ui-frontend} frontend
'';
nativeBuildInputs = [ makeWrapper ];
postInstall = ''
wrapProgram $out/bin/yt-dlp-web-ui \
--prefix PATH : ${lib.makeBinPath [ yt-dlp ]}
'';
vendorHash = "sha256-guM/U9DROJMx2ctPKBQis1YRhaf6fKvvwEWgswQKMG0=";
meta = common.meta // {
mainProgram = "yt-dlp-web-ui";
};
}

20
nix/tests/default.nix Normal file
View File

@@ -0,0 +1,20 @@
{ self, pkgs }: {
testServiceStarts = pkgs.testers.runNixOSTest (_: {
name = "service-starts";
nodes = {
machine = _: {
imports = [
self.nixosModules.default
];
services.yt-dlp-web-ui = {
enable = true;
downloadDir = "/var/lib/yt-dlp-web-ui";
};
};
};
testScript = ''
machine.wait_for_unit("yt-dlp-web-ui")
'';
});
}