set_default_setting('LOG_LEVEL', logging.INFO)
set_default_setting('LISTEN', '0.0.0.0')
set_default_setting('PORT', 8765)
+ set_default_setting('SYS_SETTINGS', False)
set_default_setting('MOTION_CHECK_INTERVAL', 10)
set_default_setting('CLEANUP_INTERVAL', 43200)
set_default_setting('THUMBNAILER_INTERVAL', 60)
def _test_requirements():
+ if settings.SYS_SETTINGS and os.geteuid() != 0:
+ print('SYS_SETTINGS require root privileges')
+ return False
try:
import tornado # @UnusedImport
import v4l2ctl
v4lutils = v4l2ctl.find_v4l2_ctl() is not None
+ import smbctl
+ mount_cifs = smbctl.find_mount_cifs() is not None
+
ok = True
if not tornado:
print('please install tornado (python-tornado)')
print('please install v4l-utils')
ok = False
+ if settings.SYS_SETTINGS and not mount_cifs:
+ print('please install cifs-utils')
+ ok = False
+
return ok
# change the port according to your requirements/restrictions
PORT = 8765
+# enable system settings that require root (SMB shares, WiFI setup)
+SYS_SETTINGS = False
+
# interval in seconds at which motionEye checks if motion is running
MOTION_CHECK_INTERVAL = 10
class MainHandler(BaseHandler):
@BaseHandler.auth()
def get(self):
- self.render('main.html')
+ self.render('main.html', sys_settings=getattr(settings, 'SYS_SETTINGS', False))
class ConfigHandler(BaseHandler):
--- /dev/null
+
+# Copyright (c) 2013 Calin Crisan
+# This file is part of motionEye.
+#
+# motionEye is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import logging
+import os
+import re
+import subprocess
+
+
+def find_mount_cifs():
+ try:
+ return subprocess.check_output('which mount.cifs', shell=True).strip()
+
+ except subprocess.CalledProcessError: # not found
+ return None
+
+
+def _make_mount_point(server, share, username):
+ server = re.sub('[^a-zA-Z0-9]', '_', server).lower()
+ share = re.sub('[^a-zA-Z0-9]', '_', share).lower()
+
+ if username:
+ mount_point = '/media/motioneye_%s_%s_%s' % (server, share, username)
+
+ else:
+ mount_point = '/media/motioneye_%s_%s' % (server, share)
+
+ logging.debug('making sure mount point "%s" exists' % mount_point)
+ os.makedirs(mount_point)
+
+ return mount_point
+
+
+def list_mounts():
+ logging.debug('listing smb mounts...')
+
+ mounts = []
+ with open('/proc/mounts', 'r') as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+
+ parts = line.split()
+ if len(parts) < 4:
+ continue
+
+ target = parts[0]
+ mount_point = parts[1]
+ fstype = parts[2]
+ opts = parts[3]
+
+ if fstype != 'cifs':
+ continue
+
+ match = re.match('//([^/]+)/(.+)', target)
+ if not match:
+ continue
+
+ if len(match.groups()) != 2:
+ continue
+
+ server, share = match.groups()
+
+ match = re.search('username=(\w+)', opts)
+ if match:
+ username = match.group(1)
+
+ else:
+ username = None
+
+ logging.debug('found smb mount "//%s/%s" at "%s"' % (server, share, mount_point))
+
+ mounts.append({
+ 'server': server,
+ 'share': share,
+ 'username': username,
+ 'mount_point': mount_point
+ })
+
+ return mounts
+
+
+def is_motioneye_mount(mount_point):
+ return bool(re.match('^/media/motioneye_\w+$', mount_point))
+
+
+def mount(server, share, username, password):
+ mount_point = _make_mount_point(server, share, username)
+ logging.debug('mounting "//%s/%s" at "%s"' % (server, share, mount_point))
+
+ if username:
+ opts = 'username=%s,password=%s' % (username, password)
+
+ else:
+ opts = 'guest'
+
+ try:
+ subprocess.check_call('mount.cifs //%s/%s %s -o %s' % (server, share, mount_point, opts), shell=True)
+
+ return mount_point
+
+ except subprocess.CalledProcessError:
+ logging.error('failed to mount smb share "//%s/%s" at "%s"' % (server, share, mount_point))
+
+ return False
+
+
+def umount(server, share, username):
+ mount_point = _make_mount_point(server, share, username)
+ logging.debug('unmounting "//%s/%s" from "%s"' % (server, share, mount_point))
+
+ try:
+ subprocess.check_call('umount %s' % mount_point, shell=True)
+
+ return True
+
+ except subprocess.CalledProcessError:
+ logging.error('failed to unmount smb share "//%s/%s" from "%s"' % (server, share, mount_point))
+
+ return False
position: relative;
}
-div.rpi,
-tr.rpi {
+div.hidden,
+tr.hidden {
display: none;
}
<div class="settings-section-title advanced-setting">File Storage</div>
<table class="settings advanced-setting">
- <tr class="settings-item advanced-setting rpi">
+ <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Storage Device</span></td>
<td class="settings-item-value">
<select class="styled storage" id="storageDeviceSelect">
</td>
<td><span class="help-mark" title="indicates the storage device where the image and video files will be saved">?</span></td>
</tr>
- <tr class="settings-item advanced-setting rpi">
+ <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Network Server</span></td>
<td class="settings-item-value"><input type="text" class="styled storage" id="networkServerEntry"></td>
<td><span class="help-mark" title="the address of the network server (IP address or hostname)">?</span></td>
</tr>
- <tr class="settings-item advanced-setting rpi">
+ <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Share Name</span></td>
<td class="settings-item-value"><input type="text" class="styled storage" id="networkShareNameEntry"></td>
<td><span class="help-mark" title="the name of the network share">?</span></td>
</tr>
- <tr class="settings-item advanced-setting rpi">
+ <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Share Username</span></td>
<td class="settings-item-value"><input type="text" class="styled storage" id="networkUsernameEntry"></td>
<td><span class="help-mark" title="the username to be supplied when accessing the network share (leave empty if no username is required)">?</span></td>
</tr>
- <tr class="settings-item advanced-setting rpi">
+ <tr class="settings-item advanced-setting {% if not sys_settings %}hidden{% endif %}">
<td class="settings-item-label"><span class="settings-item-label">Share Password</span></td>
<td class="settings-item-value"><input type="password" class="styled storage" id="networkPasswordEntry"></td>
<td><span class="help-mark" title="the password required by the network share (leave empty if no password is required)">?</span></td>
</tr>
</table>
- <div class="settings-section-title rpi"><input type="checkbox" class="styled section notifications" id="motionNotificationsSwitch">Motion Notifications</div>
+ <div class="settings-section-title hidden"><input type="checkbox" class="styled section notifications" id="motionNotificationsSwitch">Motion Notifications</div>
<table class="settings">
- <tr class="settings-item rpi">
+ <tr class="settings-item hidden">
<td class="settings-item-label"><span class="settings-item-label">Email Addresses</span></td>
<td class="settings-item-value"><input type="text" class="styled notifications" id="emailAddressesEntry" placeholder="email addresses..."></td>
<td><span class="help-mark" title="email addresses (separated by comma) that are added here will receive notifications whenever a motion event is detected (leave empty to disable email notifications)">?</span></td>
</tr>
</table>
- <div class="settings-section-title rpi"><input type="checkbox" class="styled section working-schedule" id="workingScheduleSwitch">Working Schedule</div>
- <table class="settings rpi">
+ <div class="settings-section-title hidden"><input type="checkbox" class="styled section working-schedule" id="workingScheduleSwitch">Working Schedule</div>
+ <table class="settings hidden">
<tr class="settings-item">
<td class="settings-item-label"><span class="settings-item-label">Monday</span></td>
<td class="settings-item-value">