Merge pull request #131255 from erdnaxe/nitter
nitter: init at unstable-2021-07-18
This commit is contained in:
commit
2eb2a255b9
6 changed files with 477 additions and 0 deletions
|
@ -536,6 +536,7 @@
|
|||
./services/misc/mwlib.nix
|
||||
./services/misc/mx-puppet-discord.nix
|
||||
./services/misc/n8n.nix
|
||||
./services/misc/nitter.nix
|
||||
./services/misc/nix-daemon.nix
|
||||
./services/misc/nix-gc.nix
|
||||
./services/misc/nix-optimise.nix
|
||||
|
|
326
nixos/modules/services/misc/nitter.nix
Normal file
326
nixos/modules/services/misc/nitter.nix
Normal file
|
@ -0,0 +1,326 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.nitter;
|
||||
configFile = pkgs.writeText "nitter.conf" ''
|
||||
${generators.toINI {
|
||||
# String values need to be quoted
|
||||
mkKeyValue = generators.mkKeyValueDefault {
|
||||
mkValueString = v:
|
||||
if isString v then "\"" + (strings.escape ["\""] (toString v)) + "\""
|
||||
else generators.mkValueStringDefault {} v;
|
||||
} " = ";
|
||||
} (lib.recursiveUpdate {
|
||||
Server = cfg.server;
|
||||
Cache = cfg.cache;
|
||||
Config = cfg.config // { hmacKey = "@hmac@"; };
|
||||
Preferences = cfg.preferences;
|
||||
} cfg.settings)}
|
||||
'';
|
||||
# `hmac` is a secret used for cryptographic signing of video URLs.
|
||||
# Generate it on first launch, then copy configuration and replace
|
||||
# `@hmac@` with this value.
|
||||
# We are not using sed as it would leak the value in the command line.
|
||||
preStart = pkgs.writers.writePython3 "nitter-prestart" {} ''
|
||||
import os
|
||||
import secrets
|
||||
|
||||
state_dir = os.environ.get("STATE_DIRECTORY")
|
||||
if not os.path.isfile(f"{state_dir}/hmac"):
|
||||
# Generate hmac on first launch
|
||||
hmac = secrets.token_hex(32)
|
||||
with open(f"{state_dir}/hmac", "w") as f:
|
||||
f.write(hmac)
|
||||
else:
|
||||
# Load previously generated hmac
|
||||
with open(f"{state_dir}/hmac", "r") as f:
|
||||
hmac = f.read()
|
||||
|
||||
configFile = "${configFile}"
|
||||
with open(configFile, "r") as f_in:
|
||||
with open(f"{state_dir}/nitter.conf", "w") as f_out:
|
||||
f_out.write(f_in.read().replace("@hmac@", hmac))
|
||||
'';
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.nitter = {
|
||||
enable = mkEnableOption "If enabled, start Nitter.";
|
||||
|
||||
server = {
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
example = "127.0.0.1";
|
||||
description = "The address to listen on.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
example = 8000;
|
||||
description = "The port to listen on.";
|
||||
};
|
||||
|
||||
https = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Set secure attribute on cookies. Keep it disabled to enable cookies when not using HTTPS.";
|
||||
};
|
||||
|
||||
httpMaxConnections = mkOption {
|
||||
type = types.int;
|
||||
default = 100;
|
||||
description = "Maximum number of HTTP connections.";
|
||||
};
|
||||
|
||||
staticDir = mkOption {
|
||||
type = types.path;
|
||||
default = "${pkgs.nitter}/share/nitter/public";
|
||||
defaultText = "\${pkgs.nitter}/share/nitter/public";
|
||||
description = "Path to the static files directory.";
|
||||
};
|
||||
|
||||
title = mkOption {
|
||||
type = types.str;
|
||||
default = "nitter";
|
||||
description = "Title of the instance.";
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
example = "nitter.net";
|
||||
description = "Hostname of the instance.";
|
||||
};
|
||||
};
|
||||
|
||||
cache = {
|
||||
listMinutes = mkOption {
|
||||
type = types.int;
|
||||
default = 240;
|
||||
description = "How long to cache list info (not the tweets, so keep it high).";
|
||||
};
|
||||
|
||||
rssMinutes = mkOption {
|
||||
type = types.int;
|
||||
default = 10;
|
||||
description = "How long to cache RSS queries.";
|
||||
};
|
||||
|
||||
redisHost = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = "Redis host.";
|
||||
};
|
||||
|
||||
redisPort = mkOption {
|
||||
type = types.port;
|
||||
default = 6379;
|
||||
description = "Redis port.";
|
||||
};
|
||||
|
||||
redisConnections = mkOption {
|
||||
type = types.int;
|
||||
default = 20;
|
||||
description = "Redis connection pool size.";
|
||||
};
|
||||
|
||||
redisMaxConnections = mkOption {
|
||||
type = types.int;
|
||||
default = 30;
|
||||
description = ''
|
||||
Maximum number of connections to Redis.
|
||||
|
||||
New connections are opened when none are available, but if the
|
||||
pool size goes above this, they are closed when released, do not
|
||||
worry about this unless you receive tons of requests per second.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
base64Media = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Use base64 encoding for proxied media URLs.";
|
||||
};
|
||||
|
||||
tokenCount = mkOption {
|
||||
type = types.int;
|
||||
default = 10;
|
||||
description = ''
|
||||
Minimum amount of usable tokens.
|
||||
|
||||
Tokens are used to authorize API requests, but they expire after
|
||||
~1 hour, and have a limit of 187 requests. The limit gets reset
|
||||
every 15 minutes, and the pool is filled up so there is always at
|
||||
least tokenCount usable tokens. Only increase this if you receive
|
||||
major bursts all the time.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
preferences = {
|
||||
replaceTwitter = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "nitter.net";
|
||||
description = "Replace Twitter links with links to this instance (blank to disable).";
|
||||
};
|
||||
|
||||
replaceYouTube = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "piped.kavin.rocks";
|
||||
description = "Replace YouTube links with links to this instance (blank to disable).";
|
||||
};
|
||||
|
||||
replaceInstagram = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Replace Instagram links with links to this instance (blank to disable).";
|
||||
};
|
||||
|
||||
mp4Playback = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable MP4 video playback.";
|
||||
};
|
||||
|
||||
hlsPlayback = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable HLS video streaming (requires JavaScript).";
|
||||
};
|
||||
|
||||
proxyVideos = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Proxy video streaming through the server (might be slow).";
|
||||
};
|
||||
|
||||
muteVideos = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Mute videos by default.";
|
||||
};
|
||||
|
||||
autoplayGifs = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Autoplay GIFs.";
|
||||
};
|
||||
|
||||
theme = mkOption {
|
||||
type = types.str;
|
||||
default = "Nitter";
|
||||
description = "Instance theme.";
|
||||
};
|
||||
|
||||
infiniteScroll = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Infinite scrolling (requires JavaScript, experimental!).";
|
||||
};
|
||||
|
||||
stickyProfile = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Make profile sidebar stick to top.";
|
||||
};
|
||||
|
||||
bidiSupport = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Support bidirectional text (makes clicking on tweets harder).";
|
||||
};
|
||||
|
||||
hideTweetStats = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Hide tweet stats (replies, retweets, likes).";
|
||||
};
|
||||
|
||||
hideBanner = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Hide profile banner.";
|
||||
};
|
||||
|
||||
hidePins = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Hide pinned tweets.";
|
||||
};
|
||||
|
||||
hideReplies = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Hide tweet replies.";
|
||||
};
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
description = ''
|
||||
Add settings here to override NixOS module generated settings.
|
||||
|
||||
Check the official repository for the available settings:
|
||||
https://github.com/zedeus/nitter/blob/master/nitter.conf
|
||||
'';
|
||||
};
|
||||
|
||||
redisCreateLocally = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Configure local Redis server for Nitter.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Open ports in the firewall for Nitter web interface.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = !cfg.redisCreateLocally || (cfg.cache.redisHost == "localhost" && cfg.cache.redisPort == 6379);
|
||||
message = "When services.nitter.redisCreateLocally is enabled, you need to use localhost:6379 as a cache server.";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.nitter = {
|
||||
description = "Nitter (An alternative Twitter front-end)";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "syslog.target" "network.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = "nitter";
|
||||
Environment = [ "NITTER_CONF_FILE=/var/lib/nitter/nitter.conf" ];
|
||||
# Some parts of Nitter expect `public` folder in working directory,
|
||||
# see https://github.com/zedeus/nitter/issues/414
|
||||
WorkingDirectory = "${pkgs.nitter}/share/nitter";
|
||||
ExecStart = "${pkgs.nitter}/bin/nitter";
|
||||
ExecStartPre = "${preStart}";
|
||||
AmbientCapabilities = lib.mkIf (cfg.server.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
|
||||
Restart = "on-failure";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
};
|
||||
|
||||
services.redis = lib.mkIf (cfg.redisCreateLocally) {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
networking.firewall = mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.server.port ];
|
||||
};
|
||||
};
|
||||
}
|
|
@ -301,6 +301,7 @@ in
|
|||
nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {};
|
||||
nginx-sso = handleTest ./nginx-sso.nix {};
|
||||
nginx-variants = handleTest ./nginx-variants.nix {};
|
||||
nitter = handleTest ./nitter.nix {};
|
||||
nix-serve = handleTest ./nix-ssh-serve.nix {};
|
||||
nix-ssh-serve = handleTest ./nix-ssh-serve.nix {};
|
||||
nixos-generate-config = handleTest ./nixos-generate-config.nix {};
|
||||
|
|
16
nixos/tests/nitter.nix
Normal file
16
nixos/tests/nitter.nix
Normal file
|
@ -0,0 +1,16 @@
|
|||
import ./make-test-python.nix ({ pkgs, ... }:
|
||||
|
||||
{
|
||||
name = "nitter";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ erdnaxe ];
|
||||
|
||||
nodes.machine = {
|
||||
services.nitter.enable = true;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("nitter.service")
|
||||
machine.wait_for_open_port("8080")
|
||||
machine.succeed("curl --fail http://localhost:8080/")
|
||||
'';
|
||||
})
|
131
pkgs/servers/nitter/default.nix
Normal file
131
pkgs/servers/nitter/default.nix
Normal file
|
@ -0,0 +1,131 @@
|
|||
{ lib
|
||||
, stdenv
|
||||
, fetchFromGitHub
|
||||
, nim
|
||||
, libsass
|
||||
}:
|
||||
|
||||
let
|
||||
jester = fetchFromGitHub {
|
||||
owner = "dom96";
|
||||
repo = "jester";
|
||||
rev = "v0.5.0";
|
||||
sha256 = "0m8a4ss4460jd2lcbqcbdd68jhcy35xg7qdyr95mh8rflwvmcvhk";
|
||||
};
|
||||
karax = fetchFromGitHub {
|
||||
owner = "karaxnim";
|
||||
repo = "karax";
|
||||
rev = "1.1.2";
|
||||
sha256 = "07ykrd21hd76vlmkqpvv5xvaxw6aaq87bky47p2420ni85a6d94j";
|
||||
};
|
||||
sass = fetchFromGitHub {
|
||||
owner = "dom96";
|
||||
repo = "sass";
|
||||
rev = "e683aa1";
|
||||
sha256 = "0qvly5rilsqqsyvr67pqhglm55ndc4nd6v90jwswbnigxiqf79lc";
|
||||
};
|
||||
regex = fetchFromGitHub {
|
||||
owner = "nitely";
|
||||
repo = "nim-regex";
|
||||
rev = "2e32fdc";
|
||||
sha256 = "1hrl40mwql7nh4wc7sdhmk8bj5728b93v5a93j49v660l0rn4qx8";
|
||||
};
|
||||
unicodedb = fetchFromGitHub {
|
||||
owner = "nitely";
|
||||
repo = "nim-unicodedb";
|
||||
rev = "v0.9.0";
|
||||
sha256 = "06j8d0bjbpv1iibqlmrac4qb61ggv17hvh6nv4pbccqk1rlpxhsq";
|
||||
};
|
||||
unicodeplus= fetchFromGitHub {
|
||||
owner = "nitely";
|
||||
repo = "nim-unicodeplus";
|
||||
rev = "v0.8.0";
|
||||
sha256 = "181wzwivfgplkqn5r4crhnaqgsza7x6fi23i86djb2dxvm7v6qxk";
|
||||
};
|
||||
segmentation = fetchFromGitHub {
|
||||
owner = "nitely";
|
||||
repo = "nim-segmentation";
|
||||
rev = "v0.1.0";
|
||||
sha256 = "007bkx8dwy8n340zbp6wyqfsq9bh6q5ykav1ywdlwykyp1n909bh";
|
||||
};
|
||||
nimcrypto = fetchFromGitHub {
|
||||
owner = "cheatfate";
|
||||
repo = "nimcrypto";
|
||||
rev = "a5742a9a214ac33f91615f3862c7b099aec43b00";
|
||||
sha256 = "0al0jsaicm8vyr63n909dq1glhvpra1n9sllmj0r7lsjsdb59wsz";
|
||||
};
|
||||
markdown = fetchFromGitHub {
|
||||
owner = "soasme";
|
||||
repo = "nim-markdown";
|
||||
rev = "abdbe5e";
|
||||
sha256 = "0f3c1sxvhbbds43c9l8cz69pfpf984msj1lv4pb7bzpxb5zil2wy";
|
||||
};
|
||||
packedjson = fetchFromGitHub {
|
||||
owner = "Araq";
|
||||
repo = "packedjson";
|
||||
rev = "7198cc8";
|
||||
sha256 = "1ay2zd88q8hvpvigsg8h0y5vc65hk3lk0d48fy9hwg4lcng19mp1";
|
||||
};
|
||||
supersnappy = fetchFromGitHub {
|
||||
owner = "guzba";
|
||||
repo = "supersnappy";
|
||||
rev = "1.1.5";
|
||||
sha256 = "1y26sgnszvdf5sn7j0jx2dpd4i03mvbk9i9ni9kbyrs798bjwi6z";
|
||||
};
|
||||
redpool = fetchFromGitHub {
|
||||
owner = "zedeus";
|
||||
repo = "redpool";
|
||||
rev = "57aeb25";
|
||||
sha256 = "0fph7qlia6fvya1zqzbgvww2hk5pd0vq1wlf9ij9jyn655mg0w3q";
|
||||
};
|
||||
frosty = fetchFromGitHub {
|
||||
owner = "disruptek";
|
||||
repo = "frosty";
|
||||
rev = "0.3.1";
|
||||
sha256 = "0hd6484ihjgl57gmqyp5xfq5prycb49k0313fqky600mhz71nmyz";
|
||||
};
|
||||
redis = fetchFromGitHub {
|
||||
owner = "zedeus";
|
||||
repo = "redis";
|
||||
rev = "94bcbf1";
|
||||
sha256 = "1p9zv4f4lqrjqa8fk98cb89b9fzlf866jc584ll9sws14904i80j";
|
||||
};
|
||||
in stdenv.mkDerivation rec {
|
||||
pname = "nitter";
|
||||
version = "unstable-2021-07-18";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "zedeus";
|
||||
repo = "nitter";
|
||||
rev = "6c5cb01b294d4f6e3b438fc47683359eb0fe5057";
|
||||
sha256 = "1dl8ndyv8m1hnydrp5xilcpp2cfbp02d5jap3y42i4nazc9ar6p4";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ nim ];
|
||||
buildInputs = [ libsass ];
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
export HOME=$TMPDIR
|
||||
nim -d:release -p:${jester} -p:${karax} -p:${sass}/src -p:${regex}/src -p:${unicodedb}/src -p:${unicodeplus}/src -p:${segmentation}/src -p:${nimcrypto} -p:${markdown}/src -p:${packedjson} -p:${supersnappy}/src -p:${redpool}/src -p:${frosty} -p:${redis}/src c src/$pname
|
||||
nim -p:${sass}/src c --hint[Processing]:off -r tools/gencss
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
install -Dt $out/bin src/$pname
|
||||
mkdir -p $out/share/nitter
|
||||
cp -r public $out/share/nitter/public
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
description = "Alternative Twitter front-end";
|
||||
homepage = "https://github.com/zedeus/nitter";
|
||||
maintainers = with maintainers; [ erdnaxe ];
|
||||
license = licenses.agpl3Only;
|
||||
platforms = [ "x86_64-linux" ];
|
||||
};
|
||||
}
|
||||
|
|
@ -7407,6 +7407,8 @@ in
|
|||
|
||||
ngrok-1 = callPackage ../tools/networking/ngrok-1 { };
|
||||
|
||||
nitter = callPackage ../servers/nitter { };
|
||||
|
||||
noice = callPackage ../applications/misc/noice { };
|
||||
|
||||
noip = callPackage ../tools/networking/noip { };
|
||||
|
|
Loading…
Reference in a new issue