parent
090305169d
commit
36c16fa7e3
@ -0,0 +1,295 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
|
||||
with lib; |
||||
|
||||
let |
||||
cfg = config.services.epgstation; |
||||
|
||||
username = config.users.users.epgstation.name; |
||||
groupname = config.users.users.epgstation.group; |
||||
|
||||
settingsFmt = pkgs.formats.json {}; |
||||
settingsTemplate = settingsFmt.generate "config.json" cfg.settings; |
||||
preStartScript = pkgs.writeScript "epgstation-prestart" '' |
||||
#!${pkgs.runtimeShell} |
||||
|
||||
PASSWORD="$(head -n1 "${cfg.basicAuth.passwordFile}")" |
||||
DB_PASSWORD="$(head -n1 "${cfg.database.passwordFile}")" |
||||
|
||||
# setup configuration |
||||
touch /etc/epgstation/config.json |
||||
chmod 640 /etc/epgstation/config.json |
||||
sed \ |
||||
-e "s,@password@,$PASSWORD,g" \ |
||||
-e "s,@dbPassword@,$DB_PASSWORD,g" \ |
||||
${settingsTemplate} > /etc/epgstation/config.json |
||||
chown "${username}:${groupname}" /etc/epgstation/config.json |
||||
|
||||
# NOTE: Use password authentication, since mysqljs does not yet support auth_socket |
||||
if [ ! -e /var/lib/epgstation/db-created ]; then |
||||
${pkgs.mysql}/bin/mysql -e \ |
||||
"GRANT ALL ON \`${cfg.database.name}\`.* TO '${username}'@'localhost' IDENTIFIED by '$DB_PASSWORD';" |
||||
touch /var/lib/epgstation/db-created |
||||
fi |
||||
''; |
||||
|
||||
streamingConfig = builtins.fromJSON (builtins.readFile ./streaming.json); |
||||
logConfig = { |
||||
appenders.stdout.type = "stdout"; |
||||
categories = { |
||||
default = { appenders = [ "stdout" ]; level = "info"; }; |
||||
system = { appenders = [ "stdout" ]; level = "info"; }; |
||||
access = { appenders = [ "stdout" ]; level = "info"; }; |
||||
stream = { appenders = [ "stdout" ]; level = "info"; }; |
||||
}; |
||||
}; |
||||
|
||||
defaultPassword = "INSECURE_GO_CHECK_CONFIGURATION_NIX\n"; |
||||
in |
||||
{ |
||||
options.services.epgstation = { |
||||
enable = mkEnableOption pkgs.epgstation.meta.description; |
||||
|
||||
usePreconfiguredStreaming = mkOption { |
||||
type = types.bool; |
||||
default = true; |
||||
description = '' |
||||
Use preconfigured default streaming options. |
||||
|
||||
Upstream defaults: |
||||
<link xlink:href="https://github.com/l3tnun/EPGStation/blob/master/config/config.sample.json"/> |
||||
''; |
||||
}; |
||||
|
||||
port = mkOption { |
||||
type = types.port; |
||||
default = 20772; |
||||
description = '' |
||||
HTTP port for EPGStation to listen on. |
||||
''; |
||||
}; |
||||
|
||||
socketioPort = mkOption { |
||||
type = types.port; |
||||
default = cfg.port + 1; |
||||
description = '' |
||||
Socket.io port for EPGStation to listen on. |
||||
''; |
||||
}; |
||||
|
||||
clientSocketioPort = mkOption { |
||||
type = types.port; |
||||
default = cfg.socketioPort; |
||||
description = '' |
||||
Socket.io port that the web client is going to connect to. This may be |
||||
different from <option>socketioPort</option> if EPGStation is hidden |
||||
behind a reverse proxy. |
||||
''; |
||||
}; |
||||
|
||||
openFirewall = mkOption { |
||||
type = types.bool; |
||||
default = false; |
||||
description = '' |
||||
Open ports in the firewall for the EPGStation web interface. |
||||
|
||||
<warning> |
||||
<para> |
||||
Exposing EPGStation to the open internet is generally advised |
||||
against. Only use it inside a trusted local network, or consider |
||||
putting it behind a VPN if you want remote access. |
||||
</para> |
||||
</warning> |
||||
''; |
||||
}; |
||||
|
||||
basicAuth = { |
||||
user = mkOption { |
||||
type = with types; nullOr str; |
||||
default = null; |
||||
example = "epgstation"; |
||||
description = '' |
||||
Basic auth username for EPGStation. If <literal>null</literal>, basic |
||||
auth will be disabled. |
||||
|
||||
<warning> |
||||
<para> |
||||
Basic authentication has known weaknesses, the most critical being |
||||
that it sends passwords over the network in clear text. Use this |
||||
feature to control access to EPGStation within your family and |
||||
friends, but don't rely on it for security. |
||||
</para> |
||||
</warning> |
||||
''; |
||||
}; |
||||
|
||||
passwordFile = mkOption { |
||||
type = types.path; |
||||
default = pkgs.writeText "epgstation-password" defaultPassword; |
||||
example = "/run/keys/epgstation-password"; |
||||
description = '' |
||||
A file containing the password for <option>basicAuth.user</option>. |
||||
''; |
||||
}; |
||||
}; |
||||
|
||||
database = { |
||||
name = mkOption { |
||||
type = types.str; |
||||
default = "epgstation"; |
||||
description = '' |
||||
Name of the MySQL database that holds EPGStation's data. |
||||
''; |
||||
}; |
||||
|
||||
passwordFile = mkOption { |
||||
type = types.path; |
||||
default = pkgs.writeText "epgstation-db-password" defaultPassword; |
||||
example = "/run/keys/epgstation-db-password"; |
||||
description = '' |
||||
A file containing the password for the database named |
||||
<option>database.name</option>. |
||||
''; |
||||
}; |
||||
}; |
||||
|
||||
settings = mkOption { |
||||
description = '' |
||||
Options to add to config.json. |
||||
|
||||
Documentation: |
||||
<link xlink:href="https://github.com/l3tnun/EPGStation/blob/master/doc/conf-manual.md"/> |
||||
''; |
||||
|
||||
default = {}; |
||||
example = { |
||||
recPriority = 20; |
||||
conflictPriority = 10; |
||||
}; |
||||
|
||||
type = types.submodule { |
||||
freeformType = settingsFmt.type; |
||||
|
||||
options.readOnlyOnce = mkOption { |
||||
type = types.bool; |
||||
default = false; |
||||
description = "Don't reload configuration files at runtime."; |
||||
}; |
||||
|
||||
options.mirakurunPath = mkOption (let |
||||
sockPath = config.services.mirakurun.unixSocket; |
||||
in { |
||||
type = types.str; |
||||
default = "http+unix://${replaceStrings ["/"] ["%2F"] sockPath}"; |
||||
example = "http://localhost:40772"; |
||||
description = "URL to connect to Mirakurun."; |
||||
}); |
||||
|
||||
options.encode = mkOption { |
||||
type = with types; listOf attrs; |
||||
description = "Encoding presets for recorded videos."; |
||||
default = [ |
||||
{ name = "H264"; |
||||
cmd = "${pkgs.epgstation}/libexec/enc.sh main"; |
||||
suffix = ".mp4"; |
||||
default = true; } |
||||
{ name = "H264-sub"; |
||||
cmd = "${pkgs.epgstation}/libexec/enc.sh sub"; |
||||
suffix = "-sub.mp4"; } |
||||
]; |
||||
}; |
||||
}; |
||||
}; |
||||
}; |
||||
|
||||
config = mkIf cfg.enable { |
||||
environment.etc = { |
||||
"epgstation/operatorLogConfig.json".text = builtins.toJSON logConfig; |
||||
"epgstation/serviceLogConfig.json".text = builtins.toJSON logConfig; |
||||
}; |
||||
|
||||
networking.firewall = mkIf cfg.openFirewall { |
||||
allowedTCPPorts = with cfg; [ port socketioPort ]; |
||||
}; |
||||
|
||||
users.users.epgstation = { |
||||
description = "EPGStation user"; |
||||
group = config.users.groups.epgstation.name; |
||||
isSystemUser = true; |
||||
}; |
||||
|
||||
users.groups.epgstation = {}; |
||||
|
||||
services.mirakurun.enable = mkDefault true; |
||||
|
||||
services.mysql = { |
||||
enable = mkDefault true; |
||||
package = mkDefault pkgs.mysql; |
||||
ensureDatabases = [ cfg.database.name ]; |
||||
# FIXME: enable once mysqljs supports auth_socket |
||||
# ensureUsers = [ { |
||||
# name = username; |
||||
# ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; }; |
||||
# } ]; |
||||
}; |
||||
|
||||
services.epgstation.settings = let |
||||
defaultSettings = { |
||||
serverPort = cfg.port; |
||||
socketioPort = cfg.socketioPort; |
||||
clientSocketioPort = cfg.clientSocketioPort; |
||||
|
||||
dbType = mkDefault "mysql"; |
||||
mysql = { |
||||
user = username; |
||||
database = cfg.database.name; |
||||
socketPath = mkDefault "/run/mysqld/mysqld.sock"; |
||||
password = mkDefault "@dbPassword@"; |
||||
connectTimeout = mkDefault 1000; |
||||
connectionLimit = mkDefault 10; |
||||
}; |
||||
|
||||
basicAuth = mkIf (cfg.basicAuth.user != null) { |
||||
user = mkDefault cfg.basicAuth.user; |
||||
password = mkDefault "@password@"; |
||||
}; |
||||
|
||||
ffmpeg = mkDefault "${pkgs.ffmpeg-full}/bin/ffmpeg"; |
||||
ffprobe = mkDefault "${pkgs.ffmpeg-full}/bin/ffprobe"; |
||||
|
||||
fileExtension = mkDefault ".m2ts"; |
||||
maxEncode = mkDefault 2; |
||||
maxStreaming = mkDefault 2; |
||||
}; |
||||
in |
||||
mkMerge [ |
||||
defaultSettings |
||||
(mkIf cfg.usePreconfiguredStreaming streamingConfig) |
||||
]; |
||||
|
||||
systemd.tmpfiles.rules = [ |
||||
"d '/var/lib/epgstation/streamfiles' - ${username} ${groupname} - -" |
||||
"d '/var/lib/epgstation/recorded' - ${username} ${groupname} - -" |
||||
"d '/var/lib/epgstation/thumbnail' - ${username} ${groupname} - -" |
||||
]; |
||||
|
||||
systemd.services.epgstation = { |
||||
description = pkgs.epgstation.meta.description; |
||||
wantedBy = [ "multi-user.target" ]; |
||||
after = [ |
||||
"network.target" |
||||
] ++ optional config.services.mirakurun.enable "mirakurun.service" |
||||
++ optional config.services.mysql.enable "mysql.service"; |
||||
|
||||
serviceConfig = { |
||||
ExecStart = "${pkgs.epgstation}/bin/epgstation start"; |
||||
ExecStartPre = "+${preStartScript}"; |
||||
User = username; |
||||
Group = groupname; |
||||
StateDirectory = "epgstation"; |
||||
LogsDirectory = "epgstation"; |
||||
ConfigurationDirectory = "epgstation"; |
||||
}; |
||||
}; |
||||
}; |
||||
} |
@ -0,0 +1,31 @@ |
||||
#!/usr/bin/env -S nix-build --no-out-link |
||||
|
||||
# Script to generate default streaming configurations for EPGStation. There's |
||||
# no need to run this script directly since generate.sh in the EPGStation |
||||
# package directory would run this script for you. |
||||
# |
||||
# Usage: ./generate | xargs cat > streaming.json |
||||
|
||||
{ pkgs ? (import ../../../../.. {}) }: |
||||
|
||||
let |
||||
sampleConfigPath = "${pkgs.epgstation.src}/config/config.sample.json"; |
||||
sampleConfig = builtins.fromJSON (builtins.readFile sampleConfigPath); |
||||
streamingConfig = { |
||||
inherit (sampleConfig) |
||||
mpegTsStreaming |
||||
mpegTsViewer |
||||
liveHLS |
||||
liveMP4 |
||||
liveWebM |
||||
recordedDownloader |
||||
recordedStreaming |
||||
recordedViewer |
||||
recordedHLS; |
||||
}; |
||||
in |
||||
pkgs.runCommand "streaming.json" { nativeBuildInputs = [ pkgs.jq ]; } '' |
||||
jq . <<<'${builtins.toJSON streamingConfig}' > $out |
||||
'' |
||||
|
||||
# vim:set ft=nix: |
@ -0,0 +1,119 @@ |
||||
{ |
||||
"liveHLS": [ |
||||
{ |
||||
"cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -flags +loop-global_header %OUTPUT%", |
||||
"name": "720p" |
||||
}, |
||||
{ |
||||
"cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -flags +loop-global_header %OUTPUT%", |
||||
"name": "480p" |
||||
}, |
||||
{ |
||||
"cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 48k -ac 2 -c:v libx264 -vf yadif,scale=-2:180 -b:v 100k -preset veryfast -maxrate 110k -bufsize 1000k -flags +loop-global_header %OUTPUT%", |
||||
"name": "180p" |
||||
} |
||||
], |
||||
"liveMP4": [ |
||||
{ |
||||
"cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1", |
||||
"name": "720p" |
||||
}, |
||||
{ |
||||
"cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1", |
||||
"name": "480p" |
||||
} |
||||
], |
||||
"liveWebM": [ |
||||
{ |
||||
"cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 3 -c:a libvorbis -ar 48000 -b:a 192k -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:720 -b:v 3000k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1", |
||||
"name": "720p" |
||||
}, |
||||
{ |
||||
"cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 2 -c:a libvorbis -ar 48000 -b:a 128k -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:480 -b:v 1500k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1", |
||||
"name": "480p" |
||||
} |
||||
], |
||||
"mpegTsStreaming": [ |
||||
{ |
||||
"cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -y -f mpegts pipe:1", |
||||
"name": "720p" |
||||
}, |
||||
{ |
||||
"cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -y -f mpegts pipe:1", |
||||
"name": "480p" |
||||
}, |
||||
{ |
||||
"name": "Original" |
||||
} |
||||
], |
||||
"mpegTsViewer": { |
||||
"android": "intent://ADDRESS#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=http;end", |
||||
"ios": "vlc-x-callback://x-callback-url/stream?url=http://ADDRESS" |
||||
}, |
||||
"recordedDownloader": { |
||||
"android": "intent://ADDRESS#Intent;package=com.dv.adm;type=video;scheme=http;end", |
||||
"ios": "vlc-x-callback://x-callback-url/download?url=http://ADDRESS&filename=FILENAME" |
||||
}, |
||||
"recordedHLS": [ |
||||
{ |
||||
"cmd": "%FFMPEG% -dual_mono_mode main -i %INPUT% -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -flags +loop-global_header %OUTPUT%", |
||||
"name": "720p" |
||||
}, |
||||
{ |
||||
"cmd": "%FFMPEG% -dual_mono_mode main -i %INPUT% -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -flags +loop-global_header %OUTPUT%", |
||||
"name": "480p" |
||||
}, |
||||
{ |
||||
"cmd": "%FFMPEG% -dual_mono_mode main -i %INPUT% -sn -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_type fmp4 -hls_fmp4_init_filename stream%streamNum%-init.mp4 -hls_segment_filename stream%streamNum%-%09d.m4s -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx265 -vf yadif,scale=-2:480 -b:v 350k -preset veryfast -tag:v hvc1 %OUTPUT%", |
||||
"name": "480p(h265)" |
||||
} |
||||
], |
||||
"recordedStreaming": { |
||||
"mp4": [ |
||||
{ |
||||
"ab": "192k", |
||||
"cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -ac 2 -c:v libx264 -vf yadif,scale=-2:720 %VB% %VBUFFER% %AB% %ABUFFER% -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1", |
||||
"name": "720p", |
||||
"vb": "3000k" |
||||
}, |
||||
{ |
||||
"ab": "128k", |
||||
"cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -ac 2 -c:v libx264 -vf yadif,scale=-2:360 %VB% %VBUFFER% %AB% %ABUFFER% -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1", |
||||
"name": "360p", |
||||
"vb": "1500k" |
||||
} |
||||
], |
||||
"mpegTs": [ |
||||
{ |
||||
"ab": "192k", |
||||
"cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -ac 2 -c:v libx264 -vf yadif,scale=-2:720 %VB% %VBUFFER% %AB% %ABUFFER% -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -y -f mpegts pipe:1", |
||||
"name": "720p (H.264)", |
||||
"vb": "3000k" |
||||
}, |
||||
{ |
||||
"ab": "128k", |
||||
"cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -ac 2 -c:v libx264 -vf yadif,scale=-2:360 %VB% %VBUFFER% %AB% %ABUFFER% -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -y -f mpegts pipe:1", |
||||
"name": "360p (H.264)", |
||||
"vb": "1500k" |
||||
} |
||||
], |
||||
"webm": [ |
||||
{ |
||||
"ab": "192k", |
||||
"cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 3 -c:a libvorbis -ar 48000 -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:720 %VB% %VBUFFER% %AB% %ABUFFER% -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1", |
||||
"name": "720p", |
||||
"vb": "3000k" |
||||
}, |
||||
{ |
||||
"ab": "128k", |
||||
"cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 2 -c:a libvorbis -ar 48000 -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:360 %VB% %VBUFFER% %AB% %ABUFFER% -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1", |
||||
"name": "360p", |
||||
"vb": "1500k" |
||||
} |
||||
] |
||||
}, |
||||
"recordedViewer": { |
||||
"android": "intent://ADDRESS#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=http;end", |
||||
"ios": "infuse://x-callback-url/play?url=http://ADDRESS" |
||||
} |
||||
} |
Loading…
Reference in new issue