-
-
Notifications
You must be signed in to change notification settings - Fork 15.4k
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
zoneminder: Path Fix #83261
zoneminder: Path Fix #83261
Conversation
Event.pm calls /bin/rm by absolute path which was not patched by nixpkgs, due to that zoneminder installs using nixos would not delete events, whether from server or zmaudit.pl
Wrong file name in patching script, was Event.pm.in, should be Event.pm
|
Missed pushing a commit that fixed that error. It's now pushed and fixed. |
@@ -115,7 +115,8 @@ in stdenv.mkDerivation rec { | |||
for f in misc/*.policy.in \ | |||
scripts/*.pl* \ | |||
scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in ; do | |||
scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in \ | |||
scripts/ZoneMinder/lib/ZoneMinder/Event.pm; do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
substituteStream(): WARNING: pattern '/usr/bin/perl' doesn't match anything in file 'scripts/ZoneMinder/lib/ZoneMinder/Event.pm'
substituteStream(): WARNING: pattern '/bin:/usr/bin' doesn't match anything in file 'scripts/ZoneMinder/lib/ZoneMinder/Event.pm'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left as is due to criticality of specified patterns and high chance of them showing again given past experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this PR you propose to do two substitutions in scripts/ZoneMinder/lib/ZoneMinder/Event.pm and neither of them was needed at the time. If we were to merge this right now, this would be removed by the next attentive person unless you provide a reference for why this is needed (e.g. "we expect it to break in the future because ...").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I initially packaged zoneminder, there were tons of replacement warnings as it was easier to simply loop through all the files that might need fixing and fix up all the paths that had to be replaced somewhere because there soooo many and having to manually specify it for each file was a royal pain. The ZM code base is just a mess.
I suggest sticking a big fat disclaimer in there about that and simply accept the warnings.
Then again, I'm also hoping that somebody will maintain ZM as I'm not using it any more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately this was a simple bit of paid work - it was hacked up locally by overwriting files in nix store, but provided back as a fix because we (me and the client) both care about upstreaming fixes.
I'd honestly prefer @peterhoeg solution of big fat warning coupled with looping over all Perl files in ZM, as the random calls to hardcoded absolute paths are a bit of a problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please amend the PR to include the note - this will be good to go then.
Result of 1 package built:
|
@peterhoeg would you approve this with a comment? |
We still need a note/comment in there explaining why we do the mass replace and why people should ignore the warnings. |
Last I tried the zoneminder module wasn't working that well either. Among other things these problems turned up:
I can reinstall zoneminder to give a better picture, but unless someone maintains this, I don't think this is in a very usable state. @peterhoeg What are you using instead? |
Motioneye. I do have a module for this which I haven't upstreamed to nixpkgs yet. |
If you can, please do. It's what I had been looking into as well. |
@mweinelt, this is what I use: { config, lib, pkgs, ... }:
let
inherit (lib) mkEnableOption mkIf mkOption types;
inherit (import <common/lib.nix> { inherit lib pkgs; }) secureService;
cfg = config.toupstream.services.motioneye;
confDir =
finalSettings.conf_path;
nonDefaultConfDir =
confDir != "/var/lib/motioneye";
toText = val: boolFn:
if builtins.isBool val
then boolFn val
else toString val;
boolToOnOff = bool:
if bool then "on" else "off";
attrsToFile = file: attrs: toBoolFn:
pkgs.writeText "${file}.conf"
(lib.concatStringsSep "\n" (lib.mapAttrsToList (k: v: "${k} ${toText v toBoolFn}") attrs));
cfgFile =
attrsToFile "motioneye" finalSettings lib.boolToString;
activeCameras =
builtins.filter (e: e.enabled) cfg.cameras;
cameraSettings = camera:
lib.recursiveUpdate
(lib.recursiveUpdate cameraDefaults
({
camera_id = camera.id;
camera_name = "cam-${toString camera.id}";
netcam_url = camera.url;
stream_port = cameraPort camera.id;
target_dir = cameraDir camera.id;
text_right = camera.location;
} // lib.optionalAttrs (! lib.hasPrefix camera.url "rtsp")
{
netcam_keepalive = true;
netcam_tolerant_check = true;
}))
camera.settings;
cameraFile = camera:
attrsToFile "camera-${toString camera.id}" (cameraSettings camera) boolToOnOff;
finalSettings = lib.recursiveUpdate defaultSettings cfg.settings;
defaultSettings = 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";
# 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
# this seems to randomly restart the whole motioneye process if one camera is unavailable
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
};
cameraDefaults = {
# @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 = false;
despeckle_filter = null;
emulate_motion = false;
event_gap = 30;
framerate = 5;
height = 720;
lightswitch_percent = 0;
locate_motion_mode = false;
locate_motion_style = "redbox";
minimum_motion_frames = 20;
movie_codec = "mkv";
movie_filename = "%Y-%m-%d/%H-%M-%S";
movie_max_time = 0;
movie_output = true;
movie_output_motion = false;
movie_passthrough = false;
movie_quality = 75;
netcam_use_tcp = true;
noise_tune = true;
# on_event_end ${cfg.package}/bin/relayevent.sh ${cfgFile} stop %t
# on_event_start ${cfg.package}/bin/relayevent.sh ${cfgFile} start %t
# on_movie_end ${cfg.package}/bin/relayevent.sh ${cfgFile} movie_end %t %f
# on_picture_save ${cfg.package}/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
picture_filename = "%Y-%m-%d/%H-%M-%S";
picture_output = "best";
picture_output_motion = false;
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 = false;
stream_maxrate = 5;
stream_motion = false;
stream_quality = 75;
text_changes = true;
text_left = "%Y-%m-%d %T";
text_scale = 2;
width = 1280;
};
camerasWithMask =
# process all masks when doing the cams - doesn't matter if enabled or not
builtins.filter (e: (builtins.hasAttr "mask_file" (cameraSettings e)) && e.mask != null) cfg.cameras;
motionConf = pkgs.writeText "motion.conf" (''
# @admin_password
# @admin_username admin
# @enabled on
# @normal_password
# @normal_username user
# @show_advanced on
setup_mode off
webcontrol_interface 1
webcontrol_localhost off
webcontrol_parms 0
webcontrol_port ${toString finalSettings.motion_control_port}
'' + lib.concatMapStringsSep "\n" (e: "camera ${(cameraFile e).name}") activeCameras);
cameraDir = id:
"${finalSettings.media_path}/Camera${toString id}";
cameraPort = id:
cfg.firstStreamingPort + id;
cfgDrv = pkgs.stdenv.mkDerivation {
name = "motioneye-config";
buildCommand = ''
dir=$out/etc/motioneye
install -Dm444 ${motionConf} $dir/${motionConf.name}
'' + lib.concatMapStringsSep "\n"
(e: "install -Dm644 ${cameraFile e} $dir/${(cameraFile e).name}")
activeCameras;
};
in
{
meta.maintainers = with lib.maintainers; [ peterhoeg ];
options.toupstream.services.motioneye = {
enable = mkEnableOption "Enable MotionEye";
openFirewall = mkOption {
description = "Open firewall";
type = types.bool;
default = true;
};
package = mkOption {
description = "Package";
type = types.package;
default = pkgs.callPackage <pkgs/motioneye> { };
};
cameras = mkOption {
description = "Cameras";
type = types.listOf (types.submodule {
options = {
enabled = mkOption {
description = "Camera";
type = types.bool;
default = true;
};
id = mkOption {
description = "Camera id";
type = types.ints.positive;
};
location = mkOption {
description = "Camera location";
type = types.str;
};
url = mkOption {
description = "Camera URL";
type = types.str;
};
settings = mkOption {
description = "Camera settings";
type = types.attrs;
default = { };
};
};
});
default = [ ];
};
settings = mkOption {
description = "Settings";
type = types.attrs;
};
firstStreamingPort = mkOption {
description = "First port for streaming cameras";
type = types.port;
default = 8080;
};
declarativeConfig = mkOption {
description = "Use declarative config only";
type = types.bool;
default = builtins.length cfg.cameras > 0;
};
retention = mkOption {
description = "How long to keep recordings. Set to '-' to keep forever.";
type = types.str;
default = "60d";
};
mqtt = {
enable = mkOption {
description = "Enable MQTT integration";
type = types.bool;
default = cfg.mqtt.username != null && cfg.mqtt.password != null;
};
username = mkOption {
description = "MQTT user name";
type = types.str;
};
password = mkOption {
description = "MQTT password";
type = types.str;
};
};
};
config = mkIf cfg.enable {
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = (with finalSettings; [
port
motion_control_port
]);
allowedTCPPortRanges = [
# let's open all
{ from = (cameraPort 1); to = (cameraPort (builtins.length cfg.cameras)); }
];
};
systemd = {
services.motioneye = secureService {
description = "MotionEye";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
DEBUG = "1";
MQTT_USER = cfg.mqtt.username;
MQTT_PASSWORD = cfg.mqtt.password;
};
serviceConfig = {
User = "motioneye";
Group = "motioneye";
ExecStartPre = pkgs.writeShellScript "motioneye-setup"
(
let
cmd =
if cfg.declarativeConfig
then "ln -sf"
else "cp --no-preserve=owner,mode";
in
''
rm -rf ${confDir}/{camera*,motion,motioneye}.conf
rm -rf ${confDir}/mask_*.pgm
${cmd} ${cfgFile} ${confDir}/motioneye.conf
for f in ${cfgDrv}/etc/motioneye/*.conf ; do
${cmd} $f ${confDir}/
done
'' + lib.concatMapStringsSep "\n"
(e: ''
${cmd} ${e.settings.mask_file} ${confDir}/mask_${toString e.id}.pgm
'')
camerasWithMask
);
ExecStart = "${cfg.package}/bin/meyectl startserver -c ${confDir}/motioneye.conf";
ReadWriteDirectories = [
finalSettings.media_path
] ++ lib.optional nonDefaultConfDir confDir;
LogsDirectory = "motioneye";
RuntimeDirectory = "motioneye";
# create /var/lib/motioneye no matter what. You can still set another conf_path if needed
StateDirectory = "motioneye";
TasksMax = 128 * (builtins.length activeCameras);
};
};
tmpfiles.rules = [
"d ${finalSettings.media_path} 0755 motioneye motioneye - -"
]
++ lib.optional nonDefaultConfDir "d ${confDir} 0755 motioneye motioneye - -"
# create all dirs, active or not
++ map (e: "d ${cameraDir e.id} 0755 motioneye motioneye ${cfg.retention} -") cfg.cameras;
};
users = {
users.motioneye = {
description = "MotionEye";
home = confDir;
isSystemUser = true;
group = "motioneye";
};
groups.motioneye = { };
};
};
} |
@peterhoeg Awesome! If you would kindly provide the package as well, that would help me alot.
|
Ah yes, sure: { fetchFromGitHub
, curl
, ffmpeg
, lib
, lsb-release
, makeWrapper
, motion
, python3Packages
, v4l_utils
, which
}:
let
pypkgs = python3Packages;
bins = [ ffmpeg lsb-release motion (v4l_utils.override { withGUI = false; }) which ];
in
pypkgs.buildPythonApplication rec {
pname = "motioneye";
version = "0.42.1";
src = fetchFromGitHub {
owner = "ccrisan";
repo = pname;
# python3
rev = "25ee8195a699b70c7c6986f702d369201ad71656";
sha256 = "sha256-1bG8Bb5u/nv7aQzsG6Bg12YUg+0GwTcBnrhWk0E7Kw4=";
};
buildInputs = bins;
postPatch = ''
substituteInPlace motioneye/scripts/relayevent.sh \
--replace curl ${curl}/bin/curl
'';
postInstall = ''
mv $out/${pypkgs.python.sitePackages}/motioneye/scripts/*.sh $out/bin
rmdir $out/${pypkgs.python.sitePackages}/motioneye/scripts
'';
# I don't know why I can't just use buildInputs
makeWrapperArgs = [
"--prefix PATH : ${lib.makeBinPath bins}"
];
propagatedBuildInputs = with pypkgs; [ jinja2 pillow pycurl pytz six tornado_5 ];
doCheck = false;
meta = with lib; {
description = "MotionEye";
maintainers = with maintainers; [ peterhoeg ];
};
} |
Event.pm calls /bin/rm by absolute path which was not patched by nixpkgs, due to that zoneminder installs using nixos would not delete events, whether from server or zmaudit.pl
Motivation for this change
Fix for #83260
Things done
sandbox
innix.conf
on non-NixOS linux)nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
./result/bin/
)nix path-info -S
before and after)