Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Second attempt at adding PeerTube package #106492

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions maintainers/maintainer-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8941,6 +8941,12 @@
githubId = 1181362;
name = "Stefan Junker";
};
stevenroose = {
email = "github@stevenroose.org";
github = "stevenroose";
githubId = 853468;
name = "Steven Roose";
};
stianlagstad = {
email = "stianlagstad@gmail.com";
github = "stianlagstad";
Expand Down
2 changes: 2 additions & 0 deletions nixos/modules/misc/ids.nix
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ in
#mailman = 316; # removed 2019-08-30
zigbee2mqtt = 317;
# shadow = 318; # unused
peertube = 319;

# When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!

Expand Down Expand Up @@ -649,6 +650,7 @@ in
#mailman = 316; # removed 2019-08-30
zigbee2mqtt = 317;
shadow = 318;
peertube = 319;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we used DynamicUser instead (combined with systemd's StateDirectory)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don't know what DynamicUser is in this case, how would that work? One thing that I know I have to do is use the tmpfiles.rules to set the datadirs of the project as being owned by the peertube user. (The actual datadir is only set in the config file, not using the module.) So I think it makes sense to have a username here that one could access using a variable in order to set certain permissions. (As long as we don't auto-generate the entire PeerTube config file from within the module. But it's really complete, I would not advise that.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DynamicUser can be used to dynamically allocate an ephemeral user/group when the service starts. That is usually possible if the users data does not need to be accessed by other users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case it would be hard, because the user needs to externally set the permission of some directories to be owned accessible by that user. I don't intend to do the full PeerTube configuration in the nixos module.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why systemd provides

RuntimeDirectory=, StateDirectory=, CacheDirectory=, LogsDirectory= or ConfigurationDirectory=

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But those directories are only used in the PeerTube's internal config file, NixOS never knows about them at the module level.


# When adding a gid, make sure it doesn't match an existing
# uid. Users and groups with the same name should have equal
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,7 @@
./services/web-apps/moodle.nix
./services/web-apps/nextcloud.nix
./services/web-apps/nexus.nix
./services/web-apps/peertube.nix
./services/web-apps/plantuml-server.nix
./services/web-apps/pgpkeyserver-lite.nix
./services/web-apps/matomo.nix
Expand Down
186 changes: 186 additions & 0 deletions nixos/modules/services/web-apps/peertube.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
{ lib, pkgs, config, ... }:

let
name = "peertube";
cfg = config.services.peertube;

uid = config.ids.uids.peertube;
gid = config.ids.gids.peertube;
in
{
options.services.peertube = {
enable = lib.mkEnableOption "Enable Peertube’s service";

user = lib.mkOption {
type = lib.types.str;
default = name;
description = "User account under which Peertube runs";
};

group = lib.mkOption {
type = lib.types.str;
default = name;
description = "Group under which Peertube runs";
};

configFile = lib.mkOption {
type = lib.types.path;
description = ''
The configuration file path for Peertube.
'';
};

database = {
createLocally = lib.mkOption {
description = "Configure local PostgreSQL database server for PeerTube.";
type = lib.types.bool;
default = true;
};

name = lib.mkOption {
type = lib.types.str;
default = "peertube_prod";
description = "Database name.";
};

user = lib.mkOption {
type = lib.types.str;
default = "peertube";
description = "Database user.";
};
};

smtp = {
createLocally = lib.mkOption {
description = "Configure local Postfix SMTP server for PeerTube.";
type = lib.types.bool;
default = true;
};
};

redis = {
createLocally = lib.mkOption {
description = "Configure local Redis server for PeerTube.";
type = lib.types.bool;
default = true;
};
};

runtimeDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/${name}";
description = "The directory where Peertube stores its runtime data.";
};

package = lib.mkOption {
type = lib.types.package;
default = pkgs.peertube;
description = ''
Peertube package to use.
'';
};
};

config = lib.mkIf cfg.enable {
users.users = lib.optionalAttrs (cfg.user == name) {
"${name}" = {
inherit uid;
group = cfg.group;
description = "Peertube user";
home = cfg.runtimeDir;
useDefaultShell = true;
# todo: fix this. needed for postgres authentication
password = "peertube";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need to be fixed I think. Does peertube not support unix socket authentication for postgresql as I think this is the approach other services take?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, it might do, but I didn't work that out yet. Yeah that's why I suggested maybe just getting the pkg in might be easier, since modules are always a bit more opinionated..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I will look into this later.

Copy link
Contributor

@mohe2015 mohe2015 Mar 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stevenroose see stevenroose#2. Feel free to squash the commits after merging. You can keep me as co-author if you like.

};
};
users.groups = lib.optionalAttrs (cfg.group == name) {
"${name}" = {
inherit gid;
};
};

services.postgresql = lib.mkIf cfg.database.createLocally {
enable = true;
ensureUsers = [ { name = cfg.database.user; }];
# The database is created as a startup script of the peertube service.
authentication = ''
host ${cfg.database.name} ${cfg.database.user} 127.0.0.1/32 trust
host ${cfg.database.name} ${cfg.database.user} 127.0.0.1/32 md5
'';
};

services.postfix = lib.mkIf cfg.smtp.createLocally {
enable = true;
};

services.redis = lib.mkIf cfg.redis.createLocally {
enable = true;
};

# Make sure the runtimeDir exists with the desired permissions.
systemd.tmpfiles.rules = [
"d \"${cfg.runtimeDir}\" - ${cfg.user} ${cfg.group} - -"
];

systemd.services.peertube = {
description = "Peertube";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "postgresql.service" "redis.service" ];
wants = [ "postgresql.service" "redis.service" ];

environment.NODE_CONFIG_DIR = "${cfg.runtimeDir}/config";
environment.NODE_ENV = "production";
environment.HOME = cfg.package;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
environment.HOME = cfg.package;
environment.HOME = cfg.package;
environment.NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an proposal but this make it work for me out of the box with self-signed certificates added to my local trust store.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, where does that magic string "/etc/ssl/certs/ca-certificates.crt" come from? Could we perhaps add this conditional on some nixos variable? Or is "/etc/ssl/certs/ca-certificates.crt" the standard location always for certs in NixOS?

Copy link
Contributor

@mohe2015 mohe2015 Mar 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/ca.nix#L85 it's called the canonical location for NixOS so should be fine.

Copy link
Contributor

@mohe2015 mohe2015 Mar 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also many other packages/modules reference it or the old /etc/ssl/certs/ca-bundle.crt one.


path = [ pkgs.nodejs pkgs.bashInteractive pkgs.ffmpeg pkgs.openssl pkgs.sudo pkgs.youtube-dl ];

script = ''
install -m 0750 -d ${cfg.runtimeDir}/config
ln -sf ${cfg.configFile} ${cfg.runtimeDir}/config/production.yaml
exec npm start
'';

serviceConfig = {
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.package;
StateDirectory = "peertube";
StateDirectoryMode = "0750";
PrivateTmp = true;
ProtectHome = true;
ProtectControlGroups = true;
ProtectSystem = "full";
Restart = "always";
Type = "simple";
TimeoutSec = 60;
CapabilityBoundingSet = "~CAP_SYS_ADMIN";
ExecStartPre = let script = pkgs.writeScript "peertube-pre-start.sh" ''
#!/bin/sh
set -e

if ! [ -e "${cfg.runtimeDir}/.first_run" ]; then
set -v
if [ -e "${cfg.runtimeDir}/.first_run_partial" ]; then
echo "Warn: first run was interrupted"
fi
touch "${cfg.runtimeDir}/.first_run_partial"

echo "Running PeerTube's PostgreSQL initialization..."
echo "PeerTube is known to work with PostgreSQL v12, if any error occurs, please check your version."

sudo -u postgres "${config.services.postgresql.package}/bin/createdb" -O ${cfg.database.user} -E UTF8 -T template0 ${cfg.database.name}
sudo -u postgres "${config.services.postgresql.package}/bin/psql" -c "CREATE EXTENSION pg_trgm;" ${cfg.database.name}
sudo -u postgres "${config.services.postgresql.package}/bin/psql" -c "CREATE EXTENSION unaccent;" ${cfg.database.name}

touch "${cfg.runtimeDir}/.first_run"
rm "${cfg.runtimeDir}/.first_run_partial"
fi
'';
in "+${script}";
};

unitConfig.RequiresMountsFor = cfg.runtimeDir;
};
};
}

30 changes: 30 additions & 0 deletions pkgs/servers/peertube/client.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{ yarnModulesConfig, mkYarnModulesFixed, server, sources, version, nodejs, stdenv }:
rec {
modules = mkYarnModulesFixed rec {
inherit version;
pname = "peertube-client-yarn-modules";
name = "${pname}-${version}";
packageJSON = "${sources}/client/package.json";
yarnLock = "${sources}/client/yarn.lock";
pkgConfig = yarnModulesConfig;
};
dist = stdenv.mkDerivation {
inherit version;
pname = "peertube-client";
src = sources;
buildPhase = ''
ln -s ${server.modules}/node_modules .
cp -a ${modules}/node_modules client/
chmod -R +w client/node_modules
patchShebangs .
npm run build:client
'';

installPhase = ''
mkdir $out
cp -a client/dist $out
'';

buildInputs = [ nodejs ];
};
}
121 changes: 121 additions & 0 deletions pkgs/servers/peertube/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
{ light ? null, pkgs, stdenv, lib, fetchurl, fetchFromGitHub, callPackage
, nodejs ? pkgs.nodejs-12_x
, jq, youtube-dl, nodePackages, yarn2nix-moretea }:

let
version = "3.0.1";

source = fetchFromGitHub {
owner = "Chocobozzz";
repo = "PeerTube";
rev = "v${version}";
sha256 = "bIXt5bQiBBlNDFXYzcdQA8qp4nse5epUx/XQOguDOX8=";
};

patchedSource = stdenv.mkDerivation {
pname = "peertube";
inherit version;
src = source;
phases = [ "unpackPhase" "patchPhase" "installPhase" ];
patches = [ ./fix_yarn_lock.patch ];
installPhase = ''
mkdir $out
cp -a . $out/
'';
};
yarnModulesConfig = {
bcrypt = {
buildInputs = [ nodePackages.node-pre-gyp ];

postInstall = let
bcrypt_version = "5.0.0";
bcrypt_lib = fetchurl {
url = "https://github.com/kelektiv/node.bcrypt.js/releases/download/v${bcrypt_version}/bcrypt_lib-v${bcrypt_version}-napi-v3-linux-x64-glibc.tar.gz";
sha256 = "0j3p2px1xb17sw3gpm8l4apljajxxfflal1yy552mhpzhi21wccn";
};
in ''
if [ "${bcrypt_version}" != "$(cat package.json | ${jq}/bin/jq -r .version)" ]; then
echo "Mismatching version please update bcrypt in derivation"
false
fi
mkdir -p lib/binding && tar -C lib/binding -xf ${bcrypt_lib}
patchShebangs ../node-pre-gyp
npm run install
'';
};

utf-8-validate = {
buildInputs = [ nodePackages.node-gyp-build ];
};

youtube-dl = {
postInstall = ''
mkdir bin
ln -s ${youtube-dl}/bin/youtube-dl bin/youtube-dl
cat > bin/details <<EOF
{"version":"${youtube-dl.version}","path":null,"exec":"youtube-dl"}
EOF
'';
};
};

mkYarnModulesFixed = args: (yarn2nix-moretea.mkYarnModules args).overrideAttrs(old: {
# This hack permits to workaround the fact that the yarn.lock
# file doesn't respect the semver requirements
buildPhase = builtins.replaceStrings [" ./package.json"] [" /dev/null; cp deps/*/package.json ."] old.buildPhase;
});

server = callPackage ./server.nix {
inherit version yarnModulesConfig mkYarnModulesFixed;
sources = patchedSource;
};

client = callPackage ./client.nix {
inherit server version yarnModulesConfig mkYarnModulesFixed;
sources = patchedSource;
};

in stdenv.mkDerivation rec {
inherit version;
pname = "peertube";
src = patchedSource;

buildPhase = ''
ln -s ${server.modules}/node_modules .
rm -rf dist && cp -a ${server.dist}/dist dist
rm -rf client/dist && cp -a ${client.dist}/dist client/
'';

installPhase = ''
mkdir $out
cp -a * $out
ln -s /tmp $out/.cache
'';

meta = {
description = "A free software to take back control of your videos";

longDescription = ''
PeerTube aspires to be a decentralized and free/libre alternative to video
broadcasting services.
PeerTube is not meant to become a huge platform that would centralize
videos from all around the world. Rather, it is a network of
inter-connected small videos hosters.
Anyone with a modicum of technical skills can host a PeerTube server, aka
an instance. Each instance hosts its users and their videos. In this way,
every instance is created, moderated and maintained independently by
various administrators.
You can still watch from your account videos hosted by other instances
though if the administrator of your instance had previously connected it
with other instances.
'';

license = lib.licenses.agpl3Plus;

homepage = "https://joinpeertube.org/";
platforms = lib.platforms.unix;

maintainers = with lib.maintainers; [ immae stevenroose ];
};
}

Loading