Skip to content

Instantly share code, notes, and snippets.

@peterhoeg
Created June 24, 2019 00:39
Show Gist options
  • Save peterhoeg/f9000a258afb8b73a6a427475cef9116 to your computer and use it in GitHub Desktop.
Save peterhoeg/f9000a258afb8b73a6a427475cef9116 to your computer and use it in GitHub Desktop.
{ 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