Created
June 24, 2019 00:39
-
-
Save peterhoeg/f9000a258afb8b73a6a427475cef9116 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ config, lib, pkgs, fetchPypi, ... }: | |
let | |
py = pkgs.python2; | |
pypkgs = py.pkgs; | |
log = pkgs.writeScript "log.sh" '' | |
#!${pkgs.runtimeShell} | |
set -euo pipefail | |
FILE=/var/log/motioneye/events.log | |
event=''${1:-"no_event"} | |
camera=''${2:-"no_camera"} | |
filename=''${3:-"no_file"} | |
echo "$(date) $event $camera $filename" >> $FILE | |
''; | |
toText = val: | |
if (builtins.isBool val) then | |
if val then "true" else "false" | |
else toString val; | |
attrsToFile = file: attrs: pkgs.writeText file (lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: | |
"${k} ${toText v}") attrs)); | |
baseStreamingPort = 8080; | |
retention = "60d"; | |
cfgFile = attrsToFile "motioneye.conf" cfg; | |
cfg = rec { | |
# path to the configuration directory (must be writable by motionEye) | |
conf_path = "/var/lib/motioneye"; | |
run_path = "/run/motioneye"; | |
log_path = "/var/log/motioneye"; | |
media_path = "/storage/DATA/motioneye"; | |
# the log level (use quiet, error, warning, info or debug) | |
log_level = "info"; | |
# the IP address to listen on | |
# (0.0.0.0 for all interfaces, 127.0.0.1 for localhost) | |
listen = "0.0.0.0"; | |
# the TCP port to listen on | |
port = 8765; | |
# path to the motion binary to use (automatically detected if commented) | |
motion_binary = "${pkgs.motion}/bin/motion"; | |
# whether motion HTTP control interface listens on | |
# localhost or on all interfaces | |
motion_control_localhost = true; | |
# the TCP port that motion HTTP control interface listens on | |
motion_control_port = 7999; | |
# interval in seconds at which motionEye checks if motion is running | |
motion_check_interval = 10; | |
# whether to restart the motion daemon when an error occurs while communicating with it | |
motion_restart_on_errors = false; | |
# interval in seconds at which motionEye checks the SMB mounts | |
mount_check_interval = 300; | |
# interval in seconds at which the janitor is called | |
# to remove old pictures and movies | |
cleanup_interval = 0; | |
# timeout in seconds to wait for response from a remote motionEye server | |
remote_request_timeout = 10; | |
# timeout in seconds to wait for mjpg data from the motion daemon | |
mjpg_client_timeout = 10; | |
# timeout in seconds after which an idle mjpg client is removed | |
# (set to 0 to disable) | |
mjpg_client_idle_timeout = 10; | |
smb_shares = false; | |
smb_mount_root = "/media"; | |
local_time_file = /etc/localtime; | |
# enables shutdown and rebooting after changing system settings | |
# (such as wifi settings or time zone) | |
enable_reboot = false; | |
# timeout in seconds to use when talking to the SMTP server | |
smtp_timeout = 60; | |
# timeout in seconds to wait for media files list | |
list_media_timeout = 120; | |
# timeout in seconds to wait for media files list, when sending emails | |
list_media_timeout_email = 10; | |
# timeout in seconds to wait for zip file creation | |
zip_timeout = 500; | |
# timeout in seconds to wait for timelapse creation | |
timelapse_timeout = 500; | |
# enable adding and removing cameras from UI | |
add_remove_cameras = true; | |
# enables HTTP basic authentication scheme (in addition to, not instead of the signature mechanism) | |
http_basic_auth = false; | |
# overrides the hostname (useful if motionEye runs behind a reverse proxy) | |
# server_name motionEye | |
}; | |
camera = camera: let | |
id = toString camera.id; | |
boolToStr = bool: if bool then "on" else "off"; | |
in pkgs.writeText "camera-${id}.conf" '' | |
# @clean_cloud_enabled off | |
# @enabled on | |
# @id ${id} | |
# @manual_record off | |
# @manual_snapshots on | |
# @motion_detection on | |
# @network_password | |
# @network_server | |
# @network_share_name | |
# @network_smb_ver 1.0 | |
# @network_username | |
# @preserve_movies 0 | |
# @preserve_pictures 0 | |
# @storage_device custom-path | |
# @upload_enabled off | |
# @upload_location | |
# @upload_method post | |
# @upload_movie on | |
# @upload_password | |
# @upload_picture on | |
# @upload_port | |
# @upload_server | |
# @upload_service ftp | |
# @upload_subfolders on | |
# @upload_username | |
# @webcam_resolution 100 | |
# @webcam_server_resize off | |
# @working_schedule | |
# @working_schedule_type outside | |
auto_brightness off | |
camera_id ${id} | |
camera_name cam-${id} | |
despeckle_filter | |
emulate_motion off | |
event_gap 30 | |
framerate 5 | |
height 720 | |
lightswitch_percent 0 | |
locate_motion_mode off | |
locate_motion_style redbox | |
mask_file | |
minimum_motion_frames 20 | |
movie_codec mkv | |
movie_filename %Y-%m-%d/%H-%M-%S | |
movie_max_time 0 | |
movie_output ${boolToStr camera.saveMovie} | |
movie_output_motion off | |
movie_passthrough on | |
movie_quality 75 | |
netcam_keepalive on | |
netcam_tolerant_check on | |
netcam_url ${camera.url} | |
noise_level ${toString camera.noise_level} | |
noise_tune on | |
# on_event_end ${pkg}/bin/relayevent.sh ${cfgFile} stop %t | |
# on_event_start ${pkg}/bin/relayevent.sh ${cfgFile} start %t | |
# on_movie_end ${pkg}/bin/relayevent.sh ${cfgFile} movie_end %t %f | |
# on_picture_save ${pkg}/bin/relayevent.sh ${cfgFile} picture_save %t %f | |
# these trigger several times per second | |
# on_area_detected ${log} area_detected %t | |
# on_motion_detected ${log} motion_detected %t | |
on_camera_lost ${log} camera_lost %t | |
on_camera_found ${log} camera_found %t | |
on_event_start ${log} event_start %t | |
on_event_end ${log} event_end %t | |
on_movie_start ${log} movie_start %t %f | |
on_movie_end ${log} movie_end %t %f | |
on_picture_save ${log} picture_save %t %f | |
picture_filename %Y-%m-%d/%H-%M-%S | |
picture_output best | |
picture_output_motion off | |
picture_quality 85 | |
post_capture 1 | |
pre_capture 1 | |
rotate 0 | |
smart_mask_speed 5 | |
snapshot_filename %Y-%m-%d/%H-%M-%S | |
snapshot_interval 0 | |
stream_auth_method 0 | |
stream_authentication user: | |
stream_localhost off | |
stream_maxrate 5 | |
stream_motion off | |
stream_port ${toString (cameraPort camera.id)} | |
stream_quality 75 | |
target_dir ${cameraDir camera.id} | |
text_changes on | |
text_left %Y-%m-%d %T | |
text_right ${camera.location} | |
text_scale 2 | |
threshold ${toString camera.threshold} | |
width 1280 | |
''; | |
motionConf = pkgs.writeText "motion.conf" '' | |
# @admin_password | |
# @admin_username admin | |
# @enabled on | |
# @normal_password | |
# @normal_username user | |
# @show_advanced on | |
${lib.concatStringsSep "\n" (map (e: | |
"camera camera-${toString e.id}.conf" | |
) cameras)} | |
setup_mode off | |
webcontrol_interface 1 | |
webcontrol_localhost on | |
webcontrol_parms 2 | |
webcontrol_port ${toString cfg.motion_control_port} | |
''; | |
cameraDir = id: | |
"${cfg.media_path}/${cameraDirName id}"; | |
cameraDirName = id: | |
"Camera${toString id}"; | |
cameraPort = id: | |
baseStreamingPort + id; | |
cameras = let | |
domain = "home.hoeg.com"; | |
in [ | |
{ id = 1; location = "Nursery"; url = "rtsp://cam-1.${domain}:8554/unicast"; | |
threshold = 6000; noise_level = 32; saveMovie = true; } | |
{ id = 2; location = "Lounge"; url = "rtsp://cam-2.${domain}:8554/unicast"; | |
threshold = 25000; noise_level = 64; saveMovie = false; } | |
{ id = 3; location = "TV"; url = "mjpeg://maureen.${domain}:8081/stream"; | |
threshold = 3000; noise_level = 32; saveMovie = false; } | |
]; | |
pkg = pypkgs.buildPythonApplication rec { | |
pname = "motioneye"; | |
version = "0.40"; | |
src = pypkgs.fetchPypi { | |
inherit pname version; | |
sha256 = "1nvzh6hqwmvbcffxz18mab6iadym379qjvbm509qrssxsvk17f98"; | |
}; | |
postPatch = with pkgs; '' | |
substituteInPlace motioneye/scripts/relayevent.sh \ | |
--replace curl ${curl}/bin/curl | |
''; | |
postInstall = '' | |
mv $out/${py.sitePackages}/motioneye/scripts/*.sh $out/bin | |
rmdir $out/${py.sitePackages}/motioneye/scripts | |
wrapProgram $out/bin/meyectl \ | |
--prefix PATH : ${lib.makeBinPath (with pkgs; [ ffmpeg lsb-release motion v4l_utils which])} | |
''; | |
nativeBuildInputs = with pkgs; [ makeWrapper ]; | |
propagatedBuildInputs = with pypkgs; [ | |
jinja2 pillow pycurl pytz tornado | |
]; | |
doCheck = false; | |
meta = with pkgs.stdenv.lib; { | |
description = "MotionEye"; | |
}; | |
}; | |
cfgDrv = pkgs.stdenv.mkDerivation { | |
name = "motioneye-config"; | |
buildCommand = '' | |
dir=$out/etc/motioneye | |
mkdir -p $dir | |
install -Dm644 ${motionConf} $dir/motion.conf | |
${lib.concatStringsSep "\n" (map (e: | |
"install -Dm644 ${camera e} $dir/camera-${toString e.id}.conf" | |
) cameras)} | |
''; | |
}; | |
in { | |
networking.firewall = { | |
allowedTCPPorts = [ cfg.port cfg.motion_control_port ]; | |
allowedTCPPortRanges = [ | |
{ from = (cameraPort 1); to = (cameraPort (builtins.length cameras)); } | |
]; | |
}; | |
systemd.services = { | |
motioneye = { | |
description = "MotionEye"; | |
wantedBy = [ "multi-user.target" ]; | |
preStart = '' | |
rm -rf ${cfg.conf_path}/{camera-*,motion}.conf | |
for f in ${cfgDrv}/etc/motioneye/*.conf ; do | |
ln -sf $f ${cfg.conf_path}/ | |
done | |
echo "################ NEW RUN ###############" >> /var/log/motioneye/events.log | |
${log} preStart | |
''; | |
serviceConfig = { | |
User = "motioneye"; | |
Group = "motioneye"; | |
ExecStart = "${pkg}/bin/meyectl startserver -c ${cfgFile}"; | |
Restart = "on-failure"; | |
ProtectSystem = "strict"; | |
ProtectHome = "tmpfs"; | |
PrivateTmp = true; | |
RemoveIPC = true; | |
NoNewPrivileges = true; | |
RestrictSUIDSGID = true; | |
LogsDirectory = "motioneye"; | |
RuntimeDirectory = "motioneye"; | |
StateDirectory = [ "motioneye" ]; | |
ReadWriteDirectories = [ cfg.media_path ]; | |
}; | |
}; | |
}; | |
systemd.tmpfiles.rules = [ | |
"d ${cfg.media_path} 0755 motioneye motioneye - -" | |
] ++ map (e: "d ${cameraDir e.id} 0755 motioneye motioneye ${retention} -") cameras; | |
users = { | |
users.motioneye = { | |
description = "MotionEye"; | |
home = cfg.conf_path; | |
isSystemUser = true; | |
group = "motioneye"; | |
}; | |
groups.motioneye = {}; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment