1
0
Fork 0

Compare commits

...

10 commits

15 changed files with 2361 additions and 2199 deletions

17
build.sh Executable file
View file

@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
BASE_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
cd "${BASE_DIR}"
rm -rf result
mkdir result
echo "Building Frontend"
nix build -v .#piped-frontend -o result/piped-frontend "$@"
echo "Building Backend"
nix build -v .#piped-backend -o result/piped-backend "$@"
echo "Building Proxy"
nix build -v .#piped-proxy -o result/piped-proxy "$@"

View file

@ -18,11 +18,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1693985761,
"narHash": "sha256-K5b+7j7Tt3+AqbWkcw+wMeqOAWyCD1MH26FPZyWXpdo=",
"lastModified": 1694422566,
"narHash": "sha256-lHJ+A9esOz9vln/3CJG23FV6Wd2OoOFbDeEs4cMGMqc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0bffda19b8af722f8069d09d8b6a24594c80b352",
"rev": "3a2786eea085f040a66ecde1bc3ddc7099f6dbeb",
"type": "github"
},
"original": {
@ -59,11 +59,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1692799911,
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {

View file

@ -20,22 +20,27 @@
nixosModules.piped = import ./module/default.nix;
nixosModules.default = self.nixosModules.piped;
overlays.piped = final: prev: {
overlays.piped = final: _prev: rec {
piped-frontend = final.callPackage ./packages/frontend {};
piped-backend = final.callPackage ./packages/backend {
jre = final.openjdk19_headless;
jdk = final.openjdk19;
};
piped-proxy = final.callPackage ./packages/proxy {};
piped-backend-deps = final.callPackage ./packages/backend/deps.nix {
jdk = final.openjdk19;
};
};
overlays.default = self.overlays.piped;
}
// utils.lib.eachSystem (utils.lib.defaultSystems) (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [self.overlay];
overlays = [self.overlays.default];
};
in {
formatter = pkgs.alejandra;
packages = {
inherit (pkgs) piped-frontend;
inherit (pkgs) piped-backend;

View file

@ -1,15 +1,15 @@
{
"frontend": {
"rev": "caa3e83ab83c93d117fe89649b7834d46ce9d53d",
"sha256": "sha256-nsLrSJlQBJ//qubPN1kHDlLcK7iDgJh5nMcNybFygeY="
"rev": "42792d8ec4a24e9a85af5320a71a3a09e0ee941c",
"sha256": "sha256-/iyHvUgV1doH0adB1jcXWj6zi30k8pmCa0RrAmHmmo8="
},
"backend": {
"rev": "a8e6907305a2f2f29d88e6cd07a2d392ece922af",
"sha256": "sha256-QJPyz//CtJB5e7egrvemYh/hBjlN58QRpVb8KAJtSZ4=",
"deps-sha256": "sha256-CS6gu7U8loktSh5xLq98vnBFWHuuv9sLYmgAZtrdP4Y="
"rev": "af6325b53791ed4f82632ad0c8736c71f0736362",
"sha256": "sha256-VBl5FgHeYO4xrNNP/Yipf210rMJpgZwQFJMsLuEkuwQ=",
"deps-sha256": "sha256-hvrA+IdNFw60QGdf8OdNl+fOz0/EJORp0TSJhg+huSA="
},
"proxy": {
"rev": "e642b968a8d73e8af281fa9a70a6798393670ac0",
"sha256": "sha256-LzCsUIjOl5D96tVW9K9zng/xiYCMthpFWtV5qX5WrEo="
"rev": "884df6a08e4eacf9494be085f225770307d2c9d2",
"sha256": "sha256-OkA6JVLPK6pog3cByCCAdfBOF5UOrtV4GoeLKGaYOWo="
}
}

View file

@ -4,58 +4,76 @@
pkgs,
...
}: let
inherit (lib.modules) mkIf;
inherit (lib.modules) mkIf mkDefault;
inherit (lib.trivial) boolToString;
inherit (lib.attrsets) mapAttrsToList optionalAttrs;
inherit (lib.strings) optionalString concatStringsSep;
inherit (pkgs) writeText writeShellScript;
inherit (pkgs) writeText;
cfg = config.services.piped;
backendConfig = cfg.backend;
nginxConfig = backendConfig.nginx;
databaseConfig = backendConfig.database;
proxyConfig = cfg.proxy;
frontendConfig = cfg.frontend;
backendSettings = backendConfig.settings;
sedEscapeSlashes = "sed 's#/#\\\/#'";
sedEscapeSingleQuotes = "sed \"s#'#\\\'#\"";
backendConfig =
backendConfigAttrs =
{
PORT = cfg.internalBackendPort;
HTTP_WORKERS = cfg.httpWorkers;
PROXY_PART = "https://${cfg.proxyDomain}";
API_URL = "https://${cfg.backendDomain}";
FRONTEND_URL = "https://${cfg.frontendDomain}";
DISABLE_REGISTRATION = cfg.disableRegistrations;
COMPROMISED_PASSWORD_CHECK = cfg.enableCompromisedPasswordCheck;
FEED_RETENTION = cfg.feedRetentionDays;
SUBSCRIPTIONS_EXPIRY = cfg.subscriptionRetentionDays;
SPONSORBLOCK_SERVERS = concatStringsSep "," cfg.sponsorblockServers;
DISABLE_RYD = cfg.disableRYD;
DISABLE_LBRY = cfg.disableLBRYStreams;
RYD_PROXY_URL = cfg.rydAPIURL;
SENTRY_DSN = cfg.sentryDSN;
"hibernate.connection.url" = "jdbc:postgresql://${cfg.postgresDBHost}:${toString cfg.postgresDBPort}/${cfg.postgresDBName}";
"hibernate.connection.driver_class" = "org.postgresql.Driver";
"hibernate.dialect" = "org.hibernate.dialect.PostgreSQLDialect";
"hibernate.connection.username" = "${cfg.postgresDBUsername}";
"hibernate.connection.password" =
if cfg.postgresDBPasswordFile == null
then cfg.postgresDBPassword
else "POSTGRES_PASSWORD_PLACEHOLDER";
PORT = backendConfig.internalPort;
PROXY_PART = "https://${proxyConfig.domain}";
API_URL = "https://${backendConfig.domain}";
FRONTEND_URL = "https://${frontendConfig.domain}";
DISABLE_REGISTRATION = backendSettings.disableRegistrations;
FEED_RETENTION = backendSettings.feedRetentionDays;
SUBSCRIPTIONS_EXPIRY = backendSettings.subscriptionRetentionDays;
HTTP_WORKERS = backendSettings.httpWorkers;
SPONSORBLOCK_SERVERS = concatStringsSep "," backendSettings.sponsorblockServers;
DISABLE_LBRY = backendSettings.disableLBRYStreams;
COMPROMISED_PASSWORD_CHECK = backendSettings.enableCompromisedPasswordCheck;
DISABLE_RYD = backendConfig.ryd.disable;
RYD_PROXY_URL = backendConfig.ryd.apiURL;
SENTRY_DSN = backendSettings.sentryDSN;
"hibernate.connection.url" = let
inherit (databaseConfig) host port name databaseOptions;
in "jdbc:postgresql://${host}:${toString port}/${name}${databaseOptions}";
"hibernate.connection.driver_class" = databaseConfig.driverClass;
"hibernate.dialect" = databaseConfig.dialect;
"hibernate.connection.username" = "${databaseConfig.username}";
}
// (optionalAttrs cfg.enableCaptcha {
CAPTCHA_API_URL = cfg.captchaAPIURL;
// (optionalAttrs databaseConfig.usePassword {
"hibernate.connection.password" =
if databaseConfig.passwordFile == null
then databaseConfig.password
else "DATABASE_PASSWORD_PLACEHOLDER";
})
// (optionalAttrs backendConfig.captcha.enable {
CAPTCHA_API_URL = backendConfig.captcha.apiURL;
# This is substituted in the PreStart of piped-backend.service
CAPTCHA_API_KEY =
if cfg.captchaAPIKeyFile != ""
if backendConfig.captcha.apiKeyFile != null
then "CAPTCHA_API_KEY_FILE_PLACEHOLDER"
else cfg.captchaAPIKey;
else backendSettings.apiKey;
})
// (optionalAttrs cfg.enableFederation {
MATRIX_SERVER = cfg.matrixServerAddr;
// (optionalAttrs backendConfig.federation.enable {
MATRIX_SERVER = backendConfig.federation.matrixServerAddr;
# also substituted
MATRIX_TOKEN =
if cfg.matrixTokenFile != ""
if backendConfig.federation.matrixTokenFile != ""
then "MATRIX_TOKEN_FILE_PLACEHOLDER"
else cfg.matrixToken;
});
else backendConfig.federation.matrixToken;
})
// backendSettings.extraSettings;
cfgValueToString = value:
if builtins.isBool value
@ -69,43 +87,64 @@
backendConfigFile =
writeText "config.properties"
(cfgToString backendConfig);
(cfgToString backendConfigAttrs);
in {
config = mkIf (cfg.enable && !cfg.disableBackend) {
systemd.tmpfiles.rules = ["d /run/piped-backend - piped piped"];
config = mkIf (cfg.enable && backendConfig.enable) {
systemd.tmpfiles.rules = [
"d /run/piped-backend - piped piped"
"f /run/piped-backend/config.properties 660 piped piped"
];
# I ran into some weird issues with ExecStartPre in piped-backend for this script
# so moved it to its own unit to run before
# some sort of syscall issue with
systemd.services.piped-backend-config-init = {
serviceConfig.User = "piped";
wantedBy = ["piped-backend.service"];
script = let
confFile = "/run/piped-backend/config.properties";
in ''
cat ${backendConfigFile} > ${confFile}
${optionalString
(backendConfig.captcha.enable && backendConfig.captcha.apiKeyFile != null) ''
VALUE="$(cat ${backendConfig.captcha.apiKeyFile} | ${sedEscapeSlashes})"
sed -i "s/CAPTCHA_API_KEY_FILE_PLACEHOLDER/$VALUE/" ${confFile}
''}
${optionalString
(backendConfig.federation.enable && backendConfig.federation.matrixTokenFile != null) ''
VALUE="$(cat ${backendConfig.federation.matrixTokenFile} | ${sedEscapeSlashes})"
sed -i "s/MATRIX_TOKEN_FILE_PLACEHOLDER/$VALUE/" ${confFile}
''}
${optionalString
(databaseConfig.passwordFile != null) ''
VALUE=$(cat ${databaseConfig.passwordFile} | ${sedEscapeSlashes})
sed -i "s/DATABASE_PASSWORD_PLACEHOLDER/$VALUE/" ${confFile}
''}
chown piped:piped ${confFile}
'';
};
systemd.services.piped-backend = {
wantedBy = ["multi-user.target"];
wants = [
"piped-backend-config-init.service"
];
after = [
"piped-backend-config-init.service"
];
serviceConfig = {
WorkingDirectory = "/run/piped-backend";
ExecStartPre = let
confFile = "/run/piped-backend/config.properties";
in
writeShellScript "piped-backend-init" ''
[ -f "${confFile}" ] && rm ${confFile}
cp ${backendConfigFile} ${confFile}
chmod 660 ${confFile}
${optionalString (cfg.enableCaptcha && cfg.captchaAPIKeyFile != "") ''
sed -i "s/CAPTCHA_API_KEY_FILE_PLACEHOLDER/$(cat ${cfg.captchaAPIKeyFile} | ${sedEscapeSlashes})/" ${confFile}
''}
${optionalString
(cfg.enableFederation && cfg.matrixTokenFile != "") ''
sed -i "s/MATRIX_TOKEN_FILE_PLACEHOLDER/$(cat ${cfg.matrixTokenFile} | ${sedEscapeSlashes})/" ${confFile}
''}
${optionalString
(cfg.postgresDBPasswordFile != null) ''
sed -i "s/POSTGRES_PASSWORD_PLACEHOLDER/$(cat ${cfg.postgresDBPasswordFile} | ${sedEscapeSlashes})/" ${confFile}
''}
'';
ExecStart = "${pkgs.piped-backend}/bin/piped-backend";
ExecStart = "${backendConfig.package}/bin/piped-backend";
RestartSec = "5s";
User = "piped";
WorkingDirectory = "/run/piped-backend";
CapabilityBoundingSet = "";
PrivateDevices = true;
PrivateUsers = true;
@ -119,43 +158,30 @@ in {
};
};
# Set password for piped when running postgres as piped won't run with
# passwordless authentication
systemd.services.piped-password = mkIf (!cfg.disablePostgresDB) {
serviceConfig = {
Type = "oneshot";
User = "postgres";
};
wantedBy = ["piped-backend.service"];
wants = ["postgresql.service"];
after = ["postgresql.service"];
script = ''
${config.services.postgresql.package}/bin/psql -c "ALTER USER ${cfg.postgresDBUsername} WITH PASSWORD '${
if cfg.postgresDBPasswordFile != null
then "$(cat ${cfg.postgresDBPasswordFile} | ${sedEscapeSingleQuotes})"
else cfg.postgresDBPassword
}';"
'';
};
services.postgresql = mkIf (!cfg.disablePostgresDB) {
services.postgresql = mkIf (!databaseConfig.disablePostgresDB) {
enable = true;
ensureUsers = [
{
name = cfg.postgresDBUsername;
ensurePermissions."DATABASE ${cfg.postgresDBName}" = "ALL PRIVILEGES";
name = databaseConfig.username;
ensurePermissions."DATABASE ${databaseConfig.name}" = "ALL PRIVILEGES";
}
];
ensureDatabases = [cfg.postgresDBName];
ensureDatabases = [
databaseConfig.name
];
authentication = mkIf (databaseConfig.password == "") ''
host ${databaseConfig.name} ${databaseConfig.username} samehost peer map=${databaseConfig.name}
'';
};
services.nginx.virtualHosts."${cfg.backendDomain}" = mkIf (!cfg.disableNginx) {
forceSSL = cfg.nginxForceSSL;
enableACME = cfg.nginxEnableACME;
services.nginx = mkIf (!nginxConfig.disableNginx) {
enable = true;
virtualHosts."${backendConfig.domain}" = {
forceSSL = mkDefault nginxConfig.forceSSL;
enableACME = mkDefault nginxConfig.enableACME;
locations."/" = {
proxyPass = "http://127.0.0.1:${toString cfg.internalBackendPort}";
proxyPass = "http://127.0.0.1:${toString backendConfig.internalPort}";
};
};
};
};

View file

@ -1,138 +1,217 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib) types;
inherit (lib.modules) mkIf;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.options) mkOption mkEnableOption mkPackageOption;
cfg = config.services.piped;
in {
options.services.piped = {
enable = mkEnableOption "piped";
frontendDomain = mkOption {
frontend = {
# Enable implies turning on nginx for frontend as it is static files
enable = mkOption {
type = types.bool;
default = cfg.enable;
};
package = mkPackageOption pkgs "piped-frontend" {};
domain = mkOption {
type = types.str;
description = "Domain where the frontend will be displayed";
};
backendDomain = mkOption {
type = types.nullOr types.str;
description = "Domain where the backend will be hosted. Set to null for project's default backend";
nginx = {
forceSSL = mkOption {
type = types.bool;
default = true;
};
proxyDomain = mkOption {
enableACME = mkOption {
type = types.bool;
default = true;
};
};
};
backend = {
enable = mkOption {
type = types.bool;
default = cfg.enable;
};
package = mkPackageOption pkgs "piped-backend" {};
domain = mkOption {
type = types.str;
description = "Domain used for the proxy server";
description = "Domain used for the backend server";
};
internalPort = mkOption {
type = types.number;
default = 3001;
};
nginx = {
disableNginx = mkOption {
type = types.bool;
default = false;
description = "Turn off module's nginx servers, this means you will need to manually proxy everything";
description = "Turn off nginx for proxying backend, use backend.internalPort instead";
};
nginxForceSSL = mkOption {
forceSSL = mkOption {
type = types.bool;
default = false;
description = "Should SSL/TLS be force enabled by nginx";
default = true;
};
nginxEnableACME = mkOption {
enableACME = mkOption {
type = types.bool;
default = false;
description = "Should ACME be force enabled by nginx";
};
disableFrontend = mkOption {
type = types.bool;
default = false;
description = "Don't host frontend";
};
disableBackend = mkOption {
type = types.bool;
default = false;
description = "Don't host backend";
};
disableProxy = mkOption {
type = types.bool;
default = false;
description = "Don't host proxy";
default = true;
};
};
database = {
disablePostgresDB = mkOption {
type = types.bool;
default = false;
description = "Manually configure postgres instead";
description = "Manually configure a database instead";
};
postgresHost = mkOption {
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Host for postgres";
description = "Database host";
};
postgresPort = mkOption {
port = mkOption {
type = types.number;
default = 5432;
description = "Port for postgres";
description = "Database port";
};
postgresDBName = mkOption {
name = mkOption {
type = types.str;
default = "piped";
description = "Database name for piped";
description = "Database name";
};
postgresDBUsername = mkOption {
username = mkOption {
type = types.str;
default = "piped";
description = "Host postgres is on";
description = "Database username";
};
postgresDBPassword = mkOption {
usePassword = mkOption {
type = types.bool;
default = true;
description = "Should use database password? If false then use passwordless auth or configure cert-based auth in databaseOptions";
};
password = mkOption {
type = types.str;
default = "password";
description = "Password to use for postgres";
default = "";
description = "Database password (default is to allow passwordless auth for samehost for piped when empty)";
};
postgresDBPasswordFile = mkOption {
passwordFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Database password file, loaded at runtime";
};
databaseOptions = mkOption {
type = types.str;
default = "";
description = "Appended to end of ";
};
driverClass = mkOption {
type = types.str;
default = "org.postgresql.Driver";
description = "The driver class to be used for the database connection";
};
dialect = mkOption {
type = types.str;
default = "org.hibernate.dialect.PostgreSQLDialect";
example = "org.hibernate.dialect.CockroachDialect";
description = "The dialect class to be used for the database connection";
};
};
captcha = {
enable = mkEnableOption "piped-backend-captcha";
apiURL = mkOption {
type = types.str;
default = "";
description = "API URL for Captcha (unknown protocol/standard, upstream project uses api.capmonster.cloud in example config)";
};
apiKey = mkOption {
type = types.str;
default = "";
description = "API Key for Captcha";
};
apiKeyFile = mkOption {
type = types.nullOr types.str;
default = null;
description = "Password file to use for postgres, loaded at runtime";
description = "API Key File for Captcha";
};
};
proxyIPv4Only = mkOption {
ryd = {
disable = mkOption {
type = types.bool;
default = false;
description = "Only use IPv4 when querying youtube's servers";
description = "Disables querying a Return YouTube Dislike server";
};
proxyNginxExtraConfig = mkOption {
type = types.lines;
default = ''
proxy_buffering on;
proxy_buffers 1024 16k;
proxy_set_header X-Forwarded-For "";
proxy_set_header CF-Connecting-IP "";
proxy_hide_header "alt-svc";
sendfile on;
sendfile_max_chunk 512k;
tcp_nopush on;
aio threads=default;
aio_write on;
directio 16m;
proxy_hide_header Cache-Control;
proxy_hide_header etag;
proxy_http_version 1.1;
proxy_set_header Connection keep-alive;
proxy_max_temp_file_size 32m;
access_log off;
'';
description = "Extra config for nginx on piped-proxy";
apiURL = mkOption {
type = types.str;
default = "https://ryd-proxy.kavin.rocks";
description = "API URL for a Return YouTube Dislike server";
};
};
federation = {
# for Piped's Federation Shenanigan
# https://github.com/TeamPiped/piped-federation#how-to-join
enable = mkEnableOption "piped-backend-federation";
matrixServerAddr = mkOption {
type = types.str;
default = "";
description = "Matrix server address for federation";
};
matrixToken = mkOption {
type = types.str;
default = "";
description = "Matrix access token";
};
matrixTokenFile = mkOption {
type = types.nullOr types.str;
default = null;
description = "Matrix access token file";
};
};
settings = {
extraSettings = mkOption {
type = types.attrs;
default = {};
description = "extra settings to pass to config.properties";
};
disableRegistrations = mkOption {
type = types.bool;
default = false;
description = "Disable user registrations";
};
httpWorkers = mkOption {
@ -159,16 +238,10 @@ in {
description = "Days subscriptions are stored for unauthenticated users";
};
disableRegistrations = mkOption {
type = types.bool;
default = true;
description = "Disable user registrations";
};
disableLBRYStreams = mkOption {
type = types.bool;
default = false;
description = "Disable showing streams provided by LBRY Youtube Partnership";
description = "Disable showing streams provided by LBRY";
};
enableCompromisedPasswordCheck = mkOption {
@ -177,98 +250,64 @@ in {
description = "Use the haveibeenpwned API to check if user password have been compromised";
};
enableCaptcha = mkOption {
type = types.bool;
default = true;
description = "Enable captcha for registrations";
};
sentryDSN = mkOption {
type = types.str;
default = "";
type = types.nullOr types.str;
default = null;
description = "Public DSN for sentry error reporting";
};
captchaAPIURL = mkOption {
type = types.str;
default = "";
description = "API URL for Captcha";
};
};
# TODO: Key & KeyFile should be only one or the other used
captchaAPIKey = mkOption {
type = types.str;
default = "";
description = "API Key for Captcha";
};
captchaAPIKeyFile = mkOption {
type = types.str;
default = "";
description = "API Key File for Captcha";
};
# TODO: run this, requires a go app and Tor server for proxy
#enableRYDServer = mkOption {
# type = types.bool;
# default = true;
# description = "Run a RYD Proxy Server to use";
#};
disableRYD = mkOption {
proxy = {
enable = mkOption {
type = types.bool;
#default = if cfg.enableRYDServer then false else true;
default = false;
description = "Disables querying a Return YouTube Dislike server";
default = cfg.enable;
};
rydAPIURL = mkOption {
package = mkPackageOption pkgs "piped-proxy" {};
domain = mkOption {
type = types.str;
#default = if cfg.enableRYDServer then cfg.rydProxyDomain else "https://ryd-proxy.kavin.rocks";
default = "https://ryd-proxy.kavin.rocks";
description = "API URL for a Return YouTube Dislike server";
description = "Domain used for the proxy server";
};
# for Piped's Federation Shenanigan
# https://github.com/TeamPiped/piped-federation#how-to-join
enableFederation = mkOption {
type = types.bool;
default = false;
description = "Enable federation of something";
};
matrixServerAddr = mkOption {
type = types.str;
default = "";
description = "Matrix server address for federation";
};
# TODO: make so only one of these options can be used
matrixToken = mkOption {
type = types.str;
default = "";
description = "Matrix access token";
};
matrixTokenFile = mkOption {
type = types.str;
default = "";
description = "Matrix access token file";
};
internalBackendPort = mkOption {
type = types.number;
default = 3001;
};
internalProxyPort = mkOption {
internalPort = mkOption {
type = types.number;
default = 3002;
};
nginx = {
disableNginx = mkOption {
type = types.bool;
default = false;
description = "Turn off nginx for proxying proxy, use proxy.internalPort instead, you will need the relevant extra nginx config in proxy.nix or similar for good performance";
};
config = mkIf (cfg.enable && (!cfg.disableBackend || !cfg.disableProxy)) {
forceSSL = mkOption {
type = types.bool;
default = true;
};
enableACME = mkOption {
type = types.bool;
default = true;
};
};
proxyIPv4Only = mkOption {
type = types.bool;
default = false;
description = "Only use IPv4 when querying youtube's servers";
};
};
};
config = mkIf (cfg.enable && (cfg.backend.enable || cfg.proxy.enable)) {
assertions = [
{
assertion = cfg.backend.captcha.enable && cfg.backend.captcha.apiKey != "" && cfg.backend.captcha.apiKeyFile != null -> cfg.certsDir != null;
message = "you must use either of services.piped.backend.captcha.{apiKey,apiKeyFile}";
}
];
users.users."piped" = {
isSystemUser = true;
group = "piped";
@ -280,6 +319,5 @@ in {
./backend.nix
./frontend.nix
./proxy.nix
./nginx.nix
];
}

View file

@ -1,21 +1,29 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib.modules) mkIf;
inherit (lib.modules) mkIf mkDefault;
cfg = config.services.piped;
frontendPackage =
pkgs.piped-frontend.override {backendDomain = cfg.backendDomain;};
backendConfig = cfg.backend;
frontendConfig = cfg.frontend;
nginxConfig = frontendConfig.nginx;
frontendPackage = frontendConfig.package.override {
backendDomain = backendConfig.domain;
};
in {
config = mkIf (cfg.enable && !cfg.disableFrontend && !cfg.disableNginx) {
config = mkIf (cfg.enable && frontendConfig.enable) {
# https://github.com/TeamPiped/Piped/blob/master/docker/nginx.conf
services.nginx.virtualHosts."${cfg.frontendDomain}" = {
forceSSL = cfg.nginxForceSSL;
enableACME = cfg.nginxEnableACME;
services.nginx = {
enable = true;
virtualHosts."${frontendConfig.domain}" = {
forceSSL = mkDefault nginxConfig.forceSSL;
enableACME = mkDefault nginxConfig.enableACME;
locations."/" = {
root = "${frontendPackage}/share/piped-frontend";
index = "index.html index.htm";
@ -26,4 +34,5 @@ in {
'';
};
};
};
}

View file

@ -1,11 +0,0 @@
{
config,
lib,
...
}: let
cfg = config.services.piped;
in {
config = lib.mkIf (cfg.enable && !cfg.disableNginx) {
services.nginx.enable = true;
};
}

View file

@ -1,19 +1,42 @@
{
config,
lib,
pkgs,
...
}:
with lib; let
}: let
inherit (lib.modules) mkIf mkDefault;
cfg = config.services.piped;
proxyConfig = cfg.proxy;
nginxConfig = proxyConfig.nginx;
defaultNginxExtraConfig = ''
proxy_buffering on;
proxy_buffers 1024 16k;
proxy_set_header X-Forwarded-For "";
proxy_set_header CF-Connecting-IP "";
proxy_hide_header "alt-svc";
sendfile on;
sendfile_max_chunk 512k;
tcp_nopush on;
aio threads=default;
aio_write on;
directio 16m;
proxy_hide_header Cache-Control;
proxy_hide_header etag;
proxy_http_version 1.1;
proxy_set_header Connection keep-alive;
proxy_max_temp_file_size 32m;
access_log off;
'';
in {
config = mkIf (cfg.enable && !cfg.disableProxy) {
config = mkIf (cfg.enable && proxyConfig.enable) {
systemd.services.piped-proxy = {
wantedBy = ["multi-user.target"];
environment.BIND = "0.0.0.0:${toString cfg.internalProxyPort}";
environment.IPV4_ONLY = mkIf cfg.proxyIPv4Only "1";
environment.BIND = "0.0.0.0:${toString proxyConfig.internalPort}";
environment.IPV4_ONLY = mkIf proxyConfig.proxyIPv4Only "1";
serviceConfig = {
ExecStart = "${pkgs.piped-proxy}/bin/piped-proxy";
ExecStart = "${proxyConfig.package}/bin/piped-proxy";
RestartSec = "5s";
User = "piped";
@ -31,25 +54,29 @@ in {
};
};
services.nginx.virtualHosts."${cfg.proxyDomain}" = lib.mkIf (!cfg.disableNginx) {
forceSSL = cfg.nginxForceSSL;
enableACME = cfg.nginxEnableACME;
services.nginx = mkIf (!nginxConfig.disableNginx) {
enable = true;
virtualHosts."${proxyConfig.domain}" = {
forceSSL = mkDefault nginxConfig.forceSSL;
enableACME = mkDefault nginxConfig.enableACME;
locations."/" = {
proxyPass = "http://localhost:${toString cfg.internalProxyPort}";
extraConfig =
cfg.proxyNginxExtraConfig
+ ''
proxyPass = "http://localhost:${toString proxyConfig.internalPort}";
extraConfig = ''
${defaultNginxExtraConfig}
add_header Cache-Control "public, max-age=604800";
'';
};
locations."~ (/videoplayback|/api/v4/|/api/manifest/)" = {
proxyPass = "http://localhost:${toString cfg.internalProxyPort}";
extraConfig =
cfg.proxyNginxExtraConfig
+ ''
proxyPass = "http://localhost:${toString proxyConfig.internalPort}";
extraConfig = ''
${defaultNginxExtraConfig}
add_header Cache-Control private always;
'';
};
};
};
};
}

View file

@ -8,11 +8,11 @@
perl,
writeText,
callPackage,
piped-backend-deps,
}: let
meta = builtins.fromJSON (builtins.readFile ../../meta.json);
deps =
callPackage ./deps.nix {inherit stdenv fetchFromGitHub jdk gradle perl;};
deps = piped-backend-deps;
gradleInit = writeText "init.gradle" ''
logger.lifecycle 'Replacing Maven repositories with ${deps}...'

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,9 @@
{
rustPlatform,
fetchFromGitHub,
nasm,
withWebP ? true,
withAVIF ? true,
}: let
meta = builtins.fromJSON (builtins.readFile ../../meta.json);
rev = meta.proxy.rev;
@ -15,6 +18,25 @@ in
sha256 = "${meta.proxy.sha256}";
};
buildNoDefaultFeatures = true;
buildFeatures =
[]
++ (
if withAVIF
then ["avif"]
else []
)
++ (
if withWebP
then ["webp"]
else []
);
nativeBuildInputs =
if withAVIF
then [nasm]
else [];
cargoLock = {lockFile = "${src}/Cargo.lock";};
doCheck = false;
}

52
readme.md Normal file
View file

@ -0,0 +1,52 @@
# Piped-Flake
This is a flake which allows you to run Piped on NixOS.
This should allow for more advanced and declarative configuration than the upstream docker containers. It also includes more documentation on config files.
## How to run
This should provide a working piped instance.
You can look at the options in module/default.nix for more information.
```nix
nixpkgs.overlays = [ inputs.piped-flake.overlays.default ];
imports = [ inputs.piped-flake.nixosModules.default ];
services.piped = let
baseDomain = "example.org";
in {
enable = true;
frontend = {
domain = "${baseDomain}";
};
backend = {
domain = "backend.${baseDomain}";
};
proxy = {
domain = "proxy.${baseDomain}";
};
}
```
## Supported Systems
`x86_64-linux` and `aarch64-linux` are both supported
However if you are deploying to `aarch64-linux` from a non-arm64 host via `qemu-user`` or using aarch64 builders without `--max-jobs=0` then the build of piped-backend will fail.
https://github.com/NixOS/nixpkgs/issues/255780
This appears to be a problem with upstream gradle.
For now you will have to build on a `aarch64-linux` host or use one as builder with `--max-jobs=0` to not use local host to build.
You can use the below on a `aarch64-linux` host to build and copy the built backend to your computer.
```sh
nix build .#piped-backend --system aarch64-linux
nix-copy-closure --to root@host-ip --use-substitutes $(readlink result)
```

View file

@ -16,7 +16,7 @@ json_set() {
# Frontend
old_frontend_rev=$(json_get '.frontend.rev')
new_frontend_rev=$(curl -L "https://api.github.com/repos/TeamPiped/Piped/commits" 2>/dev/null | jq ".[0].sha" -r)
if [ "$new_frontend_rev" != "$old_frontend_rev" ] || [ "${FORCE_UPDATE-}" != "" ]; then
if [ "$new_frontend_rev" != "$old_frontend_rev" ] || [ "${UPDATE-}" == "frontend" ]; then
echo "Frontend is out of date. Updating..."
json_set '.frontend.rev' "$new_frontend_rev"
json_set '.frontend.sha256' ""
@ -27,6 +27,7 @@ if [ "$new_frontend_rev" != "$old_frontend_rev" ] || [ "${FORCE_UPDATE-}" != ""
pushd Piped
git reset --hard "$new_frontend_rev"
#yarn install --no-lockfile
# yarn add unocss@0.56.4
yarn install --mode update-lockfile
nix run "github:NixOS/nixpkgs/nixos-unstable#yarn2nix" > "${BASE_DIR}/packages/frontend/yarn.nix"
cp yarn.lock "${BASE_DIR}/packages/frontend/yarn.lock"
@ -38,7 +39,7 @@ fi
# Backend
old_backend_rev=$(json_get '.backend.rev')
new_backend_rev=$(curl -L "https://api.github.com/repos/TeamPiped/Piped-Backend/commits" 2>/dev/null | jq ".[0].sha" -r)
if [ "$new_backend_rev" != "$old_backend_rev" ] || [ "${FORCE_UPDATE-}" != "" ]; then
if [ "$new_backend_rev" != "$old_backend_rev" ] || [ "${FORCE_UPDATE-}" == "backend" ]; then
echo "Backend is out of date. Updating..."
json_set '.backend.rev' "$new_backend_rev"
json_set '.backend.sha256' ""
@ -48,7 +49,7 @@ fi
# Proxy
old_proxy_rev=$(json_get '.proxy.rev')
new_proxy_rev=$(curl -L "https://api.github.com/repos/TeamPiped/piped-proxy/commits" 2>/dev/null | jq ".[0].sha" -r)
if [ "$new_proxy_rev" != "$old_proxy_rev" ] || [ "${FORCE_UPDATE-}" != "" ]; then
if [ "$new_proxy_rev" != "$old_proxy_rev" ] || [ "${FORCE_UPDATE-}" == "proxy" ]; then
echo "Proxy is out of date. Updating..."
json_set '.proxy.rev' "$new_proxy_rev"
json_set '.proxy.sha256' ""