diff --git a/.gitignore b/.gitignore index 7130cc9..2326232 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ debug # vscode config folder .vscode/ + +# markdownlint exceptions file +.markdownlint.json diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..10af237 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,490 @@ +# Distributed Motion Surveillance Security System (DMS3) Installation + +[![Go Report Card](https://goreportcard.com/badge/github.com/richbl/go-distributed-motion-s3)](https://goreportcard.com/report/github.com/richbl/go-distributed-motion-s3) +[![codebeat badge](https://codebeat.co/badges/155e9293-7023-4956-81f5-b3cde7b93842)](https://codebeat.co/projects/github-com-richbl-go-distributed-motion-s3-master) +![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/richbl/go-distributed-motion-s3?include_prereleases) + +## Installation Overview + +This document describes how to compile and install the **Distributed Motion Surveillance Security System (DMS3)** from the **DMS3** project sources. + +At a high level, these are the steps needed to install the various components of the **DMS3** project: + +1. Download the **DMS3** sources from this project +2. Compile the sources into **DMS3** component executables +3. Configure each of the **DMS3** component executables +4. Install the **DMS3** components to all participating hardware devices +5. Run the **DMS3** components + +Since **DMS3** is a distributed security system, components are installed both on a server and at any number of participating device clients, referred to as a smart device client (SDC). SDCs are typically smaller IoT devices and single-board computers (SBCs), such as a Raspberry Pi. + +The table below provides an overview of where **DMS3** components will be installed: + +| Component | Hardware | Required? | +| :--------------------------- | :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **DMS3Server** | Server (*e.g.*, headless server or desktop PC) | Yes | +| **DMS3Client** | Smart device client (SDC), such as a Raspberry Pi or similar single-board computer (SBC) | Yes, multiple clients can be installed | +| **DMS3Libs** | Server, SDCs | Yes | +| **DMS3Dashboard** | Server, SDCs | Yes (can be disabled) | +| **DMS3Mail** | SDCs | Optional, if using the [Motion](https://motion-project.github.io/) motion detection application, the **DMS3Mail** component can be installed on the SDC to manage real-time email notification of surveillance events | + +## 1. Download the **DMS3** Project + +Use the Github option to either clone or download the project on the [Github project main page](https://github.com/richbl/go-distributed-motion-s3), and set up the project locally using git. For example: + +```text +git clone https://github.com/richbl/go-distributed-motion-s3 +``` + +## 2. Compile the **DMS3** Components + +The **DMS3** project sources must first be compiled into binary executables--one for each supported hardware platform--before installation. + +**DMS3** components must be compiled for the operating system (*e.g.*, Linux) and CPU architecture (*e.g.*, AMD64) of the hardware device on which the component will be installed. If the OS and architecture are not available in the current **DMS3** release, it's very possible to configure a platform and compile as appropriate. For details on [Go](https://golang.org/ "Go") compiler support, see the [Go support for various architectures and OS platforms](https://golang.org/doc/install/source#environment "Go Support"). + +The current release of **DMS3** natively supports the following architectures: + +- Linux AMD64 +- Linux ARM6 (*e.g.*, Raspberry Pi A, A+, B, B+, Zero) +- Linux ARM7 (*e.g.*, Raspberry Pi 2, 3) +- Linux ARM8 (*e.g.*, Raspberry Pi 4) + +To compile all components of the **DMS3** project, run the `compile_dms3` command located in the `cmd` folder in the project root (*i.e.*, `go run cmd/compile_dms3/compile_dms3.go`). + +The result of a successful **DMS3** project compile is the creation of a `dms3_release` folder. The folder structure of a typical **DMS3** release is as follows: + +```shell +dms3_release/ +├── cmd +│   ├── install_dms3 +│   ├── linux_amd64 +│   │   ├── dms3client +│   │   ├── dms3client_remote_installer +│   │   ├── dms3mail +│   │   ├── dms3server +│   │   └── dms3server_remote_installer +│   ├── linux_arm6 +│   │   ├── dms3client +│   │   ├── dms3client_remote_installer +│   │   ├── dms3mail +│   │   ├── dms3server +│   │   └── dms3server_remote_installer +│   ├── linux_arm7 +│   │   ├── dms3client +│   │   ├── dms3client_remote_installer +│   │   ├── dms3mail +│   │   ├── dms3server +│   │   └── dms3server_remote_installer +│   └── linux_arm8 +│   ├── dms3client +│   ├── dms3client_remote_installer +│   ├── dms3mail +│   ├── dms3server +│   └── dms3server_remote_installer +└── config + ├── dms3build + │   └── dms3build.toml + ├── dms3client + │   ├── dms3client.service + │   └── dms3client.toml + ├── dms3dashboard + │   ├── assets + │   │   ├── css + │   │   │   ├── bootstrap.min.css + │   │   │   ├── icomoon-icons.css + │   │   │   └── paper-dashboard.css + │   │   ├── fonts + │   │   │   ├── icomoon.eot + │   │   │   ├── icomoon.svg + │   │   │   ├── icomoon.ttf + │   │   │   └── icomoon.woff + │   │   └── img + │   │   ├── dms3logo.png + │   │   ├── favicon.png + │   │   └── favicon.svg + │   ├── dms3dashboard.html + │   └── dms3dashboard.toml + ├── dms3libs + │   └── dms3libs.toml + ├── dms3mail + │   ├── assets + │   │   └── img + │   │   ├── dms3github.png + │   │   └── dms3logo.png + │   ├── dms3mail.html + │   └── dms3mail.toml + └── dms3server + ├── dms3server.service + ├── dms3server.toml + └── media + ├── motion_start.wav + └── motion_stop.wav +``` + +## 3. Configure the **DMS3** Components + +All **DMS3** components are configured through an associated text-based configuration file called a TOML ([Tom's Obvious, Minimal Language](https://github.com/toml-lang/toml)) file, and a common file extension, `*.toml`. This file is very minimal in format, but well-documented with many defaults preset, so should be generally self-explanatory, and serves the purpose of isolating user-configurable parameters from the rest of the component code. + +The table below associates the **DMS3** package/component with the relevant TOML file: + +| Package/Component | TOML File Location | +| :--------------------------- | :--------------------------------------------------- | +| **DMS3Build** | dms3_release/config/dms3build/dms3build.toml | +| **DMS3Server** | dms3_release/config/dms3server/dms3server.toml | +| **DMS3Client** | dms3_release/config/dms3client/dms3client.toml | +| **DMS3Libs** | dms3_release/config/dms3libs/dms3libs.toml | +| **DMS3Dashboard** | dms3_release/config/dms3dashboard/dms3dashboard.toml | +| **DMS3Mail** | dms3_release/config/dms3mail/dms3mail.toml | + +### **DMS3Build** Configuration + +The **DMS3Build** package is used to configure and redistribute **DMS3** components to all participating hardware devices across the end user network. This process is automated, using the `dms3build` executable compiled in the preceding step. This executable relies on a separate configuration file, `dms3build.toml` located at `dms3_release/config/dms3build/dms3build.toml`. + +#### Edit the **DMS3Build** Configuration File (`dms3build.toml`) + +This configuration file is broken into two separate sections, `[Clients]` and `[Servers]`, and then further subdivided into sections for each of the hardware devices onto which **DMS3** components will be installed. + +For the `[Clients]` section, a sub-section needs to be completed for each hardware device. These sub-sections are numbered sequentially (*e.g.*, `[Clients.0]`, `[Clients.1]`... `[Clients.n]`). + +- `Clients.0.User`: user of the hardware device (*e.g.*, pi) +- `Clients.0.DeviceName`: fully qualified domain name of the hardware device (*e.g.*, picam-alpha.local) +- `Clients.0.SSHPassword`: SSH password to remote into the device. Leave blank if using an SSH certificate instead +- `Clients.0.RemoteAdminPassword`: admin password on the remote device (required for component installation) +- `Clients.0.Port`: SSH port for remote access +- `Clients.0.Platform`: platform type of the remote device (*e.g.*, "linuxArm7"). `dms3build` will use this to copy over the correct binary executable + +For the `[Servers]` section, a sub-section needs to be completed for each hardware device acting as a **DMS3** server. As of this release, only one active **DMS3** server has been tested. + +- `Servers.0.User`: user of the hardware device (*e.g.*, richbl) +- `Servers.0.DeviceName`: fully qualified domain name of the hardware device (*e.g.*, main.local) +- `Servers.0.SSHPassword`: SSH password to remote into the device. Leave blank if using an SSH certificate instead +- `Servers.0.RemoteAdminPassword`: admin password on the remote device (required for component installation) +- `Servers.0.Port`: SSH port for remote access +- `Servers.0.Platform`: platform type of the remote device (*e.g.*, "linuxArm7"). `dms3build` will use this to copy over the correct binary executable + +### **DMS3Client** & **DMS3Server** Configurations + +For both **DMS3Client** and **DMS3Server** components, configuration files must first be edited before getting redistributed across the end user network using the `dms3build` executable compiled in the previous step. + +> While **DMS3Client** and **DMS3Server** components have their own configuration file--`dms3client.toml` and `dms3server.toml`, respectively--they also share two additional configuration files from the **DMS3Dashboard** and **DMS3Libs** components. + +#### Edit the **DMS3Client** Configuration File (`dms3client.toml`) + +By default, this file is installed into `/etc/distributed-motion-s3/dms3client` on each Smart Device Client (SDC) and used for configuring the following: + +- `Server.IP`: the address on which the **DMS3Server** is running +- `Server.Port`: the port on which the **DMS3Server** is running +- `Server.CheckInterval`: the interval (in seconds) for checking the **DMS3Server** +- `Logging.LogLevel`: sets the log levels for application logging +- `Logging.LogDevice`: determines to what device logging should be output +- `Logging.LogFilename`: filename of the **DMS3Client** log +- `Logging.LogLocation`: location of logfile (absolute path; must have full r/w permissions) + +#### Edit the **DMS3Server** Configuration File (`dms3server.toml`) + +By default, this file is installed into `/etc/distributed-motion-s3/dms3server` on the server, used for setting the following: + +- `Server.Port`: port on which to run the **DMS3Server** +- `Server.CheckInterval`: the interval (in seconds) between checks for change to motion state +- `Server.EnableDashboard`: start and display the HTML dashboard template +- `Audio.Enable`: enable the play-back of audio on motion detector application start/stop +- `Audio.PlayMotionStart`: the audio file to play when the motion detector application starts +- `Audio.PlayMotionEnd`: the audio file to play when the motion detector application stops +- `AlwaysOn.Enable`: toggle the time-based *Always On* feature +- `AlwaysOn.TimeRange`: set the range (24-hour format) to start/stop the *Always On* feature +- `UserProxy.IPBase`: the first three address octets defining the network (*e.g.*, 10.10.10.) where user proxies (devices representing users on the network, such as a smartphone) will be scanned to determine when the motion detector application should be run +- `UserProxy.IPRange`: the fourth address octet defined as the network range (*e.g.*, 100, 254) +- `UserProxy.MacsToFind`: the MAC addresses (*e.g.*, "24:da:9b:0d:53:8f") of user proxy device(s) to search for on the LAN +- `Logging.LogLevel`: sets the log levels for application logging +- `Logging.LogDevice`: determines to what device logging should be output +- `Logging.LogFilename`: filename of the **DMS3Server** log +- `Logging.LogLocation`: location of logfile (absolute path; must have full r/w permissions) + +### **DMS3Dashboard** Configuration + +Shared between both **DMS3Client** and **DMS3Server**, this file is installed into `/etc/distributed-motion-s3/dms3dashboard` on both the server and each participating device client. + +#### Edit the **DMS3Dashboard** Configuration File (`dms3dashboard.toml`) + +The specific **DMS3Dashboard** settings for the **DMS3Client** component are as follows: + +- `Client.ImagesFolder`: the location where the motion detection application stores its motion-triggered image/movie files on the client + +The specific **DMS3Dashboard** settings for the **DMS3Server** component are as follows: + +- `Server.Port`: setting the port on which to run the dashboard HTML server +- `Server.Filename`: filename of HTML dashboard template file +- `Server.FileLocation`: where the HTML dashboard template file is located +- `Server.Title`: the dashboard title (displayed in the browser) +- `Server.Resort`: toggle to alphabetically re-sort of devices displayed in the dashboard template +- `Server.ServerFirst`: toggle to make the **DMS3Server** the first of all devices displayed in the dashboard template +- `Server.DeviceStatus`: device status identifies the stages when a device is no longer reporting status updates to the dashboard server, as status health is represented graphically on the dashboard + +### **DMS3Libs** Configuration + +By default, shared by all **DMS3** components, this file is installed into `/etc/distributed-motion-s3/dms3libs` on both server and participating device clients, and used to configure the location of system-level commands (*e.g.*, `ping`). + +#### Edit the **DMS3Libs** Configuration File (`dms3libs.toml`) + +This file maps command name to absolute pathname, as follows: + +- `SysCommands`: + - `APLAY` = "/usr/bin/aplay" + - `BASH` = "/usr/bin/bash" + - `CAT` = "/usr/bin/cat" + - `ENV` = "/usr/bin/env" + - `GREP` = "/usr/bin/grep" + - `IP` = "/usr/sbin/ip" + - `PGREP` = "/usr/bin/pgrep" + - `PING` = "/usr/bin/ping" + - `PKILL` = "/usr/bin/pkill" + +### Optional: **DMS3Mail** Configuration + +**DMS3Mail** is a stand-alone client-side component responsible for generating and sending an email whenever a valid motion event is triggered in the [Motion](https://motion-project.github.io/) application. + +#### Edit the **DMS3Mail** Configuration File (`dms3mail.toml`) + +By default, this file is installed into `/etc/distributed-motion-s3/dms3mail` on each participating device client running the **DMS3Client** component, and used for setting the following: + +- `Filename`: filename of HTML email template file +- `FileLocation`: where the HTML email template file is located +- `Email.From`: the email sender +- `Email.To`: the email recipient +- `SMTP.Address`: the host of the SMTP server +- `SMTP.Port`: the port of the SMTP server +- `SMTP.Username`: the username to use to authenticate to the SMTP server +- `SMTP.Password`: the password to use to authenticate to the SMTP server +- `Logging.LogLevel`: sets the log levels for application logging +- `Logging.LogDevice`: determines to what device logging should be output +- `Logging.LogFilename`: filename of the **DMS3Mail** log +- `Logging.LogLocation`: location of logfile (absolute path; must have full r/w permissions) + +## 4. Install the **DMS3** Components + +With all **DMS3** component configuration files properly edited, the **DMS3Build** component can be used to automatically distribute and set up **DMS3** components across the end user network, as defined in the **DMS3Build** configuration file (`dms3build.toml`). + +> Be sure to have all participating hardware devices online and available when running the **DMS3Build** component, as it will attempt to sequentially perform a remote login and file transfers to every device identified in the `dms3build.toml` file using the authentication details provided + +The **DMS3Build** `install_dms3` binary, located in `dms3_release/cmd` does the following: + +1. Copies all platform-specific binaries, services, configurations, and media files to the designated device platform. Review the `dms3_release` folder for an understanding of what these files are and their location +2. Copies a remote installer executable (*i.e.*, `dms3client_remote_installer` or `dms3server_remote_installer`) to that device platform +3. Runs the remote installer, which in turn, redistributes **DMS3** component binaries and files into their respective default locations on that remote device +4. Upon successful completion, deletes all installer support files, and finally, the remote installer + +### Run the **DMS3Build** Installer + +To install all configured **DMS3** components, run `install_dms3` (*i.e.*, `./dms3_release/cmd/install_dms3`): + +```text +go run /dms3_release/cmd/install_dms3 +``` + +The `dms3build` installer will display the installation progress on all device platforms. On completion, these device platforms will be properly configured to run **DMS3** components. + +The table below provides a good overview of where **DMS3Build** installs the various **DMS3** component files: + +| Component Element | Default Location | Configurable Location? | +| :-------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | +| [Go](https://golang.org/ "Go") executable (*e.g.*, `dms3client`) | `usr/local/bin` | Yes, install anywhere on [`$PATH`](http://www.linfo.org/path_env_var.html "PATH environment variable") | +| [TOML](https://en.wikipedia.org/wiki/TOML "TOML") config file (*e.g.*, `dms3client.toml`) | `/etc/distributed-motion-s3/` | Yes, edit in [Go](https://golang.org/ "Go") sources (*e.g.*, `dms3client.go`) | +| Optional: daemon service file (*e.g.*, `dms3client.service`) | None (manual install only) | No (platform-dependent) | +| Optional: log file (*e.g.*, `dms3client.log`), runtime-generated | `/var/log/dms3` | Yes, edit in [TOML](https://en.wikipedia.org/wiki/TOML "TOML") config file (*e.g.*, `dms3client.toml`) | + +### Confirm the Installation of a Motion Detection Application on **DMS3Client** Devices + +Without an operational motion detection application running on the newly configured **DMS3Client** components, **DMS3** really doesn't have much to do, though **DMS3Server** will obligingly send enable/disable messages to all participating **DMS3Client** components based on its configuration rules. As a result, and as part of the **DMS3** component installation process, the following procedure should be followed: + +1. Confirm the installation of a motion detection application on all smart device clients (SDCs) running the **DMS3Client** component, such as a Raspberry Pi or similar single board computer (SBC) + +2. If using the [Motion](https://motion-project.github.io/ "Motion") motion detection application, configure [Motion](https://motion-project.github.io/) to run as a daemon + +For proper operation with **DMS3**, [Motion](https://motion-project.github.io/) must be set to run in daemon mode (which permits [Motion](https://motion-project.github.io/) to run as a background process). This is achieved through an edit made to the `motion.conf` file located in the installed [Motion](https://motion-project.github.io/) application folder (*e.g.*, `/etc/motion`). + +In the section called "System control configuration parameters" (or similar, depending on the version of [Motion](https://motion-project.github.io/) installed), set the `daemon` variable to `on` as indicated below: + +```shell +############################################################ +# System control configuration parameters +############################################################ + +# Start in daemon (background) mode and release terminal. +daemon on +``` + +### Optional: Integrate **DMS3Mail** with Motion on **DMS3Client** Devices + +**DMS3Mail** is a stand-alone client-side component responsible for generating and sending an email whenever a valid motion event is triggered in [Motion](https://motion-project.github.io/). The **DMS3Mail** component is called by [Motion](https://motion-project.github.io/) when the [*on_picture_save*](https://motion-project.github.io/motion_config.html#on_picture_save "on_picture_save command") and the [on_movie_end](https://motion-project.github.io/motion_config.html#on_movie_end "on_movie_end command") commands (called [hooks](http://en.wikipedia.org/wiki/Hooking "Hooking")) are fired during a motion event of interest. + +> Note that **DMS3Mail** runs independently from, and has no direct dependencies on either the **DMS3Client** component (or the **DMS3Server** component). It can even be run standalone with [Motion](https://motion-project.github.io/), apart from **DMS3** entirely. + +The syntax for these [Motion](https://motion-project.github.io/) commands are: + +```shell + -pixels=%D -filename=%f +``` + +These commands are saved directly in the [Motion](https://motion-project.github.io/) configuration file called `motion.conf` (by default, located in `/etc/motion`). + +To enable [Motion](https://motion-project.github.io/) to call the **DMS3Mail** component on picture save (or movie end), follow the procedure below: + +1. Either edit the [Motion](https://motion-project.github.io/) `motion.conf` file to include the following line: + +```shell +############################################################## +# Run DMS3 Mail when image generated +############################################################## +on_picture_save /usr/local/bin/dms3mail -pixels=%D -filename=%f +``` + +OR... run the command below which echoes the `on_picture_save` line directly into the last line of the `motion.conf` file: + +```shell +sudo echo 'on_picture_save /usr/local/bin/dms3mail -pixels=%D -filename=%f >> /etc/motion/motion.conf" +``` + +2. Restart [Motion](https://motion-project.github.io/) to have the update to `motion.conf` take effect + +```shell +sudo service motion restart +``` + +**DMS3Mail** will now generate and send an email whenever [Motion](https://motion-project.github.io/) generates an `on_picture_save` (or `on_movie_end`) command. + +## 5. Run the **DMS3** Components + +With all the **DMS3** components properly configured and installed across various server and client devices, it's now possible to run **DMS3**. + +### Run **DMS3** Components as Executables + +#### Run the **DMS3Server** Component + +1. On the server, run **DMS3Server** by typing `dms3server`. The component should now be started, and if configured correctly, generating logging information either to the display or to a log file. + + An example of **DMS3Server** logging output is displayed below: + + ```shell + INFO: 2022/01/25 19:27:11 lib_log.go:81: dms3server started + INFO: 2022/01/25 19:27:11 lib_log.go:81: server started + INFO: 2022/01/25 19:27:39 lib_log.go:81: OPEN connection from: 10.10.10.183:52624 + INFO: 2022/01/25 19:27:39 lib_log.go:81: Sent dashboard enable state as: 0 + INFO: 2022/01/25 19:27:41 lib_log.go:81: /usr/sbin/ip command: no device mac address found + INFO: 2022/01/25 19:27:41 lib_log.go:81: Sent motion detector state as: 1 + INFO: 2022/01/25 19:27:41 lib_log.go:81: CLOSE connection from: 10.10.10.183:52624 + INFO: 2022/01/25 19:27:49 lib_log.go:81: OPEN connection from: 10.10.10.183:52626 + INFO: 2022/01/25 19:27:49 lib_log.go:81: Sent dashboard enable state as: 1 + INFO: 2022/01/25 19:27:50 lib_log.go:81: /usr/sbin/ip command: no device mac address found + INFO: 2022/01/25 19:27:50 lib_log.go:81: Sent motion detector state as: 1 + INFO: 2022/01/25 19:27:50 lib_log.go:81: CLOSE connection from: 10.10.10.183:52626 + ``` + + In this example, logging is set to the INFO level and is reporting that **DMS3Server** is sending out to all participating **DMS3Client** components an initial motion detector state of 0 (disabled). Shortly thereafter, and after not detecting the presence of a user proxy (*e.g.*, the end user's smartphone MAC address), the motion detector state is set to 1 (enabled). + +#### Run the **DMS3Client** Component + +1. On each of the smart clients, run **DMS3Client** by typing `dms3client`. The component should now be started, and if configured correctly, generating logging information either to the display or to a log file. + + An example of **DMS3Client** logging output is displayed below: + + ```shell + INFO: 2022/01/25 19:28:24 lib_log.go:81: dms3client started + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3client.configDashboardClientMetrics + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3dashboard.InitDashboardClient + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3dashboard.(*DeviceMetrics).checkImagesFolder + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3client.startClient + INFO: 2022/01/25 19:28:24 lib_log.go:81: OPEN connection from: 10.10.10.183:49300 + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3client.processClientRequest + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3dashboard.ReceiveDashboardRequest + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3dashboard.receiveDashboardEnableState + INFO: 2022/01/25 19:28:24 lib_log.go:81: Received dashboard enable state as: 1 + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3dashboard.sendDashboardData + INFO: 2022/01/25 19:28:24 lib_log.go:81: Sent client dashboard data + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3client.receiveMotionDetectorState + DEBUG: 2022/01/25 19:28:24 lib_log.go:90: dms3client.ProcessMotionDetectorState + INFO: 2022/01/25 19:28:24 lib_log.go:81: Process not found when running '/usr/bin/pgrep -i motion' + INFO: 2022/01/25 19:28:24 lib_log.go:81: Received motion detector state as: 0 + INFO: 2022/01/25 19:28:24 lib_log.go:81: CLOSE connection from: 10.10.10.183:49300 + ``` + + In this example, logging is set to the DEBUG level and is reporting that **DMS3Client** is receiving from the **DMS3Server** component a motion detector state of 0 (disabled). + + > Note that for meaning data to be read from the **DMS3** logging feature, both **DMS3Server** and **DMS3Client** should be running + +### Optional: Run **DMS3** Components as Services + +Running either the **DMS3Client** or **DMS3Server** components as services ([daemons](https://en.wikipedia.org/wiki/Daemon_(computing) "computing daemon")) is preferred, as these services can be configured to run at machine startup, recover from failures, *etc*. + +As different Unix-like systems use different approaches for system service management and startup, service configuration is beyond the scope of the install procedure. However, the **DMS3** project does include sample daemon files for both the **DMS3Client** and the **DMS3Server** components called `dms3client.service`, and `dms3client.service`, respectively, and located in the `dms3_release` folder at `dms3_release/dms3client` and `dms3_release/dms3server`. + +### Optional: View the **DMS3Dashboard** Component + +By default (as configured in `dms3dashboard.toml`), the **DMS3Dashboard** component is enabled and configured to run locally on the the **DMS3Server** component on port 8081. To view the **DMS3Dashboard** in a web browser, go to [localhost:8081](http://localhost:8081). +> Note that the **DMS3Server** component must be running in order to view the **DMS3Dashboard**. + +## 6. Configuration Testing & Troubleshooting + +At this point, **DMS3** should now be installed and configured on both the server and all smart device clients (SDCs). Once both the **DMS3Server** and **DMS3Client** are running, **DMS3** should: + +1. Watch for the presence of relevant user device proxies on the network at a regular interval +2. Start/stop [Motion](https://motion-project.github.io/) when relevant user device proxies join/leave the network +3. Optionally, create and send an email when an event of interest is generated by [Motion](https://motion-project.github.io/) (assuming that the **DMS3Mail** component has been installed) + +### System Testing **DMS3** + +The procedure for testing **DMS3** is to add/remove a user device proxy to/from the network (*i.e.*, enable/disable the device's networking capability), and watch (or listen, if so configured) **DMS3Server** and **DMS3Client** process motion state events. Recall that individual **DMS3** components can be configured to generate multi-level logging (INFO, ERROR, FATAL, and DEBUG) to file or [stdout](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29 "standard output"). + +### Unit Testing the **DMS3Libs** Component + +As an aid in troubleshooting issues, the **DMS3** source project tree includes a `tests` folder as part of the **DMS3Libs** component. This `tests` folder contains a number of unit tests designed to verify operation of each of the library packages used in the **DMS3Libs** component. + +To run a **DMS3Libs** component unit test, from the command line, change directory into the `tests` folder and choose a test to run: + +```shell +go test <*>.go +``` + +Where `<*>` is a Go test file. The unit test results will be displayed as each test is completed. + +## **Appendix A**: Managing Motion Capture Files + +While **DMS3** does not generate image or videos files, as part of an installed motion detection application such as [Motion](https://motion-project.github.io/), these applications can often generate a lot of files in very short order. + +One solution that we've used successfully while running **DMS3** is to install and configure the [Old-Files-Delete script](https://github.com/richbl/old-files-delete) on all client devices running the **DMS3Client** component. Properly configured, the Old-Files-Delete script will periodically as a [cron job](https://en.wikipedia.org/wiki/Cron), and keep image and video files well managed. + +From the project site: + +> **Old-Files-Delete** (`old_files_delete.sh`) is a [bash](https://en.wikipedia.org/wiki/Bash_%28Unix_shell%29) script to recursively delete files older than (n) number of days. `run_old_files_delete.sh` is a related script intended to be used for making unattended script calls into `old_files_delete.sh` (*e.g.*, running cron jobs). + +## **Appendix B**: Running **DMS3** with Less Smart Device Clients (LSDCs) + +Less smart device clients (LSDCs), such as IP cameras and webcams require special consideration in **DMS3**. + +While smart device clients (SDCs), such as a Raspberry Pi or similar single board computer (SBC), have both a camera device and a means for running a motion detection application on the same local host, LSDCs typically just have a camera device, with limited means for processing video streams locally. + +**DMS3** resolves this limitation by allowing any **DMS3Client** to serve as an *SDC proxy* for one or more LSDCs. + +Operationally, an SDC running as a proxy for one or more LSDCs is viewed no differently than a standalone SDC. However, care must be taken to make sure that all participating LSDCs are correctly enumerated when configuring the locally-installed motion detection application on the SDC proxy. + +As an example, using [Motion](https://motion-project.github.io/), the configuration file, `motion.conf`, permits multiple video devices to be managed by a single instance of [Motion](https://motion-project.github.io/). These devices can all be managed by one SDC proxy (running the **DMS3Client** component). + +In the example file below, the last few lines of a `motion.conf` file is listed, showing four separate camera configurations managed by a single SDC proxy (note the last line used by the **DMS3Mail** component): + +```shell +############################################################## +# Camera config files - One for each camera. +############################################################## +camera /home/user/security/motion_config/cam_office.conf +camera /home/user/security/motion_config/cam_livingroom.conf +camera /home/user/security/motion_config/cam_garage.conf +camera /home/user/security/motion_config/cam_driveway.conf + +############################################################## +# Run DMS3 Mail when image generated +############################################################## +on_picture_save /usr/local/bin/dms3mail -pixels=%D -filename=%f +``` + +Once configured, these devices, while technically still LSDCs, are now managed through a single SDC running the **DMS3Client** component in the context of **DMS3**. + +> **Note:** it's possible to install both a **DMS3Client** component and a **DMS3Server** component on the same machine. In this configuration, the host serves as a **DMS3** server (**DMS3Server**) for a client (**DMS3Client**) that happens to be running locally (localhost), which in turn, can serve as an SDC proxy for any number of remote LSDCs. diff --git a/LICENSE b/LICENSE index f398683..d553981 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Rich +Copyright (c) 2017-2022 Rich Bloch Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANUAL_INSTALL.md b/MANUAL_INSTALL.md deleted file mode 100644 index bc8aa84..0000000 --- a/MANUAL_INSTALL.md +++ /dev/null @@ -1,400 +0,0 @@ -# Distributed Motion Surveillance Security System (DMS3) Manual Installation - -This procedure describes how to manually install the **Distributed Motion Surveillance Security System (DMS3)** from the **DMS3** project sources. - -For details on how to quickly install **Distributed Motion Surveillance Security System (DMS3)** using the included `dms3build` process, see the [Distributed Motion Surveillance Security System (DMS3) Quick Installation](https://github.com/richbl/go-distributed-motion-s3/blob/master/QUICK_INSTALL.md) documentation. - -## Installation Overview - -The installation of **DMS3** is comprised of two steps: - -1. The installation and configuration of **DMS3** components on participating hardware devices: - - | Component | Install Location | Required? | - | :------------- | :------------- | :------------- | - | DMS3Server | server | Yes | - | DMS3Client | smart device clients (SDCs) | Yes | - | DMS3Libs | server, SDCs | Yes | - | DMS3Dashboard | server | Yes (but can be disabled) | - | DMS3Mail | SDCs | Optional(*) | - - > (*) if using the [Motion](https://motion-project.github.io/) motion detection application, the **DMS3Mail** component can be installed on the SDC to manage real-time email notification of surveillance events - -1. The installation and configuration of a motion detection application, such as [Motion](https://motion-project.github.io/ "Motion") or the [OpenCV](http://opencv.org/ "Open Source Computer Vision Library") Library - -## 1. Download/Clone the **DMS3** Project - -Use the `clone or download` button on the [Github project main page](https://github.com/richbl/go-distributed-motion-s3), and clone the project locally using git: - -``` -git clone https://github.com/richbl/go-distributed-motion-s3 -``` - -## 2. Compile **DMS3** - -The **DMS3** project sources must first be compiled into binary executables before installation. To compile all components of the **DMS3** project, run `compile_dms3` (i.e., `go run compile_dms3.go`). - -The result of a successful **DMS3** project compile is the creation of a `dms_release` folder. The folder structure of a typical **DMS3** release is as follows: - -```shell - dms3_release - ├── dms3build - │   └── dms3build.toml - ├── dms3client - │   ├── dms3client.service - │   └── dms3client.toml - ├── dms3dashboard - │   ├── assets - │   │   ├── css - │   │   │   ├── bootstrap.min.css - │   │   │   ├── icomoon-icons.css - │   │   │   └── paper-dashboard.css - │   │   ├── fonts - │   │   │   ├── icomoon.eot - │   │   │   ├── icomoon.svg - │   │   │   ├── icomoon.ttf - │   │   │   └── icomoon.woff - │   │   └── img - │   │   └── favicon.ico - │   ├── dashboard.html - │   └── dms3dashboard.toml - ├── dms3libs - │   └── dms3libs.toml - ├── dms3mail - │   └── dms3mail.toml - ├── dms3server - │   ├── dms3server.service - │   ├── dms3server.toml - │   └── media - │   ├── motion_start.wav - │   └── motion_stop.wav - ├── linux_amd64 - │   ├── dms3client_remote_installer - │   ├── dms3server_remote_installer - │   ├── go_dms3client - │   ├── go_dms3mail - │   ├── go_dms3server - │   └── install_dms3 - ├── linux_arm6 - │   ├── dms3client_remote_installer - │   ├── dms3server_remote_installer - │   ├── go_dms3client - │   ├── go_dms3mail - │   ├── go_dms3server - │   └── install_dms3 - └── linux_arm7 - ├── dms3client_remote_installer - ├── dms3server_remote_installer - ├── go_dms3client - ├── go_dms3mail - ├── go_dms3server - └── install_dms3 - -``` - -## 4. Configure **DMS3** Components - -All **DMS3** components are configured through an associated text-based configuration file called a TOML ([Tom's Obvious, Minimal Language](https://github.com/toml-lang/toml)) file, and a common file extension, `*.toml`. This file is very minimal in format, but well-documented with many defaults preset, so should be generally self-explanatory. The table below identifies the TOML file with the component: - - | Component | TOML File Location | - | :------------- | :------------- | - | DMS3Server | dms3_release/dms3server/dms3server.toml | - | DMS3Client | dms3_release/dms3client/dms3client.toml | - | DMS3Libs | dms3_release/dms3libs/dms3libs.toml | - | DMS3Dashboard | dms3_release/dms3dashboard/dms3dashboard.toml | - | DMS3Mail | dms3_release/dms3mail/dms3mail.toml | - -### **DMS3** Server Configuration - -1. Edit **DMS3** configuration files - - All server-side package components, **DMS3Server**, **DMS3Dashboard**, and **DMS3Libs** must be configured for proper operation. Each component includes a separate `*.toml` file which serves the purpose of isolating user-configurable parameters from the rest of the code: - - - `dms3server.toml`, by default installed into `/etc/distributed-motion-s3/dms3server`, is used for: - - setting the server port - - determining what devices to monitor (MAC addresses) - - determining if and when to run the *Always On* feature (set time range) - - identifying audio files used when enabling/disabling the surveillance system - - configuring component logging options - - `dms3dashboard.toml`, shared between both **DMS3Server** and **DMS3Client**, this file is installed into `/etc/distributed-motion-s3/dms3dashboard` and configures dashboard settings: - - whether the dashboard is enabled - - the local port the web server will run on - - the filename and location of the dashboard HTML file - - the banner title of the dashboard - - `dms3libs.toml`, by default installed into `/etc/distributed-motion-s3/dms3libs`, is used to configure the location of system-level commands (e.g., `ping`) - - Each configuration file is self-documenting, and provides examples of common default values. - -1. Optional: configure the server to run the **DMS3Server** component as a [daemon](https://en.wikipedia.org/wiki/Daemon_(computing) "computing daemon") - - Running the **DMS3Server** component as a [`systemd`](https://en.wikipedia.org/wiki/Systemd) service is preferred, as this service can be configured to run at machine startup, recover from failures, etc. - - As different Unix-like systems use different approaches for system service management and startup, daemon configuration is beyond the scope of the install procedure. However, the project does include a sample daemon file for running with [`systemd`](https://en.wikipedia.org/wiki/Systemd), called `dms3server.service`, located in the `dms3_release` folder at `dms3_release/dms3server`. - -### **DMS3** Smart Device Client (SDC) Configuration - -1. Edit **DMS3** configuration files - - All client-side package components--**DMS3Client**, **DMS3Dashboard**, **DMS3Libs**, and **DMS3Mail** (if installed)--must be configured for proper operation. Each component includes a separate `*.toml` file which serves the purpose of isolating user-configurable parameters from the rest of the code: - - - `dms3client.toml`, by default installed into `/etc/distributed-motion-s3/dms3client`, is used for: - - setting the server IP address and port - - setting the frequency to check **DMS3Server** for motion state changes - - configuring component logging options - - `dms3dashboard.toml`, shared between both **DMS3Server** and **DMS3Client**, this file is installed into `/etc/distributed-motion-s3/dms3dashboard` and configures dashboard settings: - - the location of where the installed motion detection application stores its motion-triggered image/movie files on the client, useful in reporting the number of events on the dashboard for each **DMS3Client** - - `dms3libs.toml`, by default installed into `/etc/distributed-motion-s3/dms3libs`, is used to configure the location of system-level commands (e.g., `ping`) - - `dms3mail.toml`, by default installed into `/etc/distributed-motion-s3/dms3mail`, if installed, is used for: - - setting email configuration options - - configuring component logging options - - Each configuration file is self-documenting, and provides examples of common default values. - -1. Optional: configure smart device client(s) to run the **DMS3Client** component as a [daemon](https://en.wikipedia.org/wiki/Daemon_(computing) "computing daemon") - - Running the **DMS3Client** component as a [`systemd`](https://en.wikipedia.org/wiki/Systemd) service is preferred, as this service can be configured to run at machine startup, recover from failures, etc. - - As different Unix-like systems use different approaches for system service management and startup, daemon configuration is beyond the scope of the install procedure. However, the project does include a sample daemon file for running with [`systemd`](https://en.wikipedia.org/wiki/Systemd), called `dms3client.service`, located in the `dms3_release` folder at `dms3_release/dms3client`. - -### **DMS3** Smart Device Client (SDC) Motion Detection Application Configuration - -Smart device clients (SDCs) are required to have a motion detection application installed and configured in order to process video streamed from its video camera device. - -**DMS3Client**, by default, is configured to run the [Motion](https://motion-project.github.io/) motion detection application (of course, [Motion](https://motion-project.github.io/) must still be installed on the device client). However, regardless of the application chosen, all **DMS3Client** configuration details are managed in one file, called `lib_detector_config.go` located in the project source tree at `go-distributed-motion-s3/dms3libs`. - -This file defines two important attributes of the configured motion detection application: - -- The command needed to run the application (e.g., `motion`) -- The possible motion states defined by the application (i.e., `Start` and `Stop`) - -In most cases when using [Motion](https://motion-project.github.io/), `lib_detector_config.go` will not require configuration. - -## 4. Install **DMS3** - -Each **DMS3** component is organized into four component elements: - -- A compiled [Go](https://golang.org/ "Go") executable (e.g., `go_dms3client`) -- A component configuration file (using the [TOML](https://en.wikipedia.org/wiki/TOML "TOML") configuration file format) -- An optional [`systemd`](https://en.wikipedia.org/wiki/Systemd) daemon service file (e.g., `dms3client.service`) -- An optional component log file, runtime-generated based on component configuration - -For proper operation, each component element must be copied into the following locations: - -| Component Element | Default Location | Configurable Location? | -| :------------- | :------------- | :------------- | -| [Go](https://golang.org/ "Go") executable (e.g., `go_dms3client`) | Anywhere on [`$PATH`](http://www.linfo.org/path_env_var.html "PATH environment variable") | Yes, install anywhere on [`$PATH`](http://www.linfo.org/path_env_var.html "PATH environment variable") (e.g., `/usr/local/bin`) | -| [TOML](https://en.wikipedia.org/wiki/TOML "TOML") config file (e.g., `dms3client.toml`) | `/etc/distributed-motion-s3/` | Yes, edit in [Go](https://golang.org/ "Go") sources (e.g., `go_dms3client.go`) -| Optional: daemon service file (e.g., `dms3client.service`) | `/etc/systemd/system` | No (platform-dependent) -| Optional: log file (e.g., `dms3client.log`), runtime-generated | `/var/log/dms3` | Yes, edit in [TOML](https://en.wikipedia.org/wiki/TOML "TOML") config file (e.g., `dms3client.toml`) - -### **DMS3Server** Installation - -The **DMS3** server component, **DMS3Server**, is responsible for the logic of enabling/disabling the video surveillance system. At a pre-configured interval, **DMS3Server** sends either a `Start` or a `Stop` message to all **DMS3** smart device clients (SDCs) listening on the network. - -To install **DMS3Server**: - -1. Copy the [Go](https://golang.org/ "Go") executable `go_dms3server` from the `dms3_release` folder into a location on the remote server reachable by the [`$PATH`](http://www.linfo.org/path_env_var.html "PATH environment variable") environment variable (e.g., `/usr/local/bin`) -1. Copy the `dms3server`, `dms3dashboard`, and `dms3libs` folders into their default locations, `/etc/distributed-motion-s3/dms3server`, `/etc/distributed-motion-s3/dms3dashboard`, and `/etc/distributed-motion-s3/dms3libs`, respectively, or as configured in `go_dms3server.go` -1. Confirm that the user running `go_dms3server` has proper permissions to create a log file (`dms3server.log`) at the default log file location `/var/log/dms3`, or as configured in `dms3server.toml` -1. Optionally, install the daemon service file (e.g., `dms3server.service`) into `/etc/systemd/system` - -### **DMS3Client** Installation - -The **DMS3** distributed client component, **DMS3Client**, runs on each smart device client, and is responsible for starting/stopping its locally installed motion detection application. - -To install **DMS3Client**: - -1. Copy the [Go](https://golang.org/ "Go") executable `go_dms3client` in the `dms3_release` folder into a location on a smart device client (SDC) reachable by the [`$PATH`](http://www.linfo.org/path_env_var.html "PATH environment variable") environment variable (e.g., `/usr/local/bin`) -1. Copy the `dms3client`, `dms3dashboard`, and `dms3libs` folders into their default locations, `/etc/distributed-motion-s3/dms3client`, `/etc/distributed-motion-s3/dms3dashboard`, and `/etc/distributed-motion-s3/dms3libs`, respectively, or as configured in `go_dms3client.go` -1. Confirm that the user running `go_dms3client` has proper permissions to create a log file (`dms3client.log`) at the default log file location `/var/log/dms3`, or as configured in `dms3client.toml` -1. Optionally, install the daemon service file (e.g., `dms3client.service`) into `/etc/systemd/system` - -A **DMS3Client** component must be installed and running on all of the smart device clients (SDCs) participating in **DMS3**. - -### **DMS3Mail** Installation (Optional) - -If a smart device client (SDC) is running the [Motion](https://motion-project.github.io/ "Motion") motion detection application, and real-time notification of surveillance events via email is desired, a **DMS3Mail** component should be installed on each participating SDC. - -To install **DMS3Mail**: - -1. Copy the [Go](https://golang.org/ "Go") executable `go_dms3mail` from the `dms3_release` folder into a location on a smart device client (SDC) reachable by the [`$PATH`](http://www.linfo.org/path_env_var.html "PATH environment variable") environment variable (e.g., `/usr/local/bin`) -1. Copy both the `dms3mail` and `dms3libs` folders into their default locations, `/etc/distributed-motion-s3/dms3mail` and `/etc/distributed-motion-s3/dms3libs`, respectively, or as configured in `go_dms3mail.go` -1. Confirm that the user running `go_dms3mail` has proper permissions to create a log file (`dms3mail.log`) at the default log file location `/var/log/dms3`, or as configured in `dms3mail.toml` - -## 5. Confirm the Installation of a Motion Detection Application on All SDCs - -Without an operational motion detection application running on the configured **DMS3Client** components, **DMS3** really doesn't have much to do, though **DMS3Server** will obligingly send enable/disable messages to all listening **DMS3Client** components based on its user proxy configuration rules. - -1. Confirm the installation of a motion detection application on all smart device clients (SDCs), such as a desktop computer, or Raspberry Pi or similar single board computer (SBC), all with an operational video camera device - -1. If using the [Motion](https://motion-project.github.io/ "Motion") motion detection application, configure [Motion](https://motion-project.github.io/) to run as a daemon - - For proper operation with **DMS3**, [Motion](https://motion-project.github.io/) must be set to run in daemon mode (which permits [Motion](https://motion-project.github.io/) to run as a background process). This is achieved through an edit made to the `motion.conf` file located in the [Motion](https://motion-project.github.io/) folder (e.g., `/etc/motion`). - - In the section called Daemon, set the `daemon` variable to `on` as noted below: - - ```shell - ############################################################ - # Daemon - ############################################################ - - # Start in daemon (background) mode and release terminal (default: off) - daemon on - ``` - -## 6. Optional: Integrate **DMS3Mail** with [Motion](https://motion-project.github.io/) on the Device Client - -**DMS3Mail** is a stand-alone client-side component responsible for generating and sending an email whenever a valid motion event is triggered in [Motion](https://motion-project.github.io/). The **DMS3Mail** component is called by [Motion](https://motion-project.github.io/) whenever the [*on_picture_save*](https://motion-project.github.io/motion_config.html#on_picture_save "on_picture_save command") and the [on_movie_end](https://motion-project.github.io/motion_config.html#on_movie_end "on_movie_end command") commands (called [hooks](http://en.wikipedia.org/wiki/Hooking "Hooking")) are fired during a motion event. - -> Note that **DMS3Mail** runs independently from, and has no dependencies upon, **DMS3Client** (or **DMS3Server**). It can be run standalone with [Motion](https://motion-project.github.io/), apart from **DMS3** entirely. - -The syntax for these [Motion](https://motion-project.github.io/) commands are: - -```shell - -pixels=%D -filename=%f -camera=%t -``` - -These commands are saved in the [Motion](https://motion-project.github.io/) configuration file called `motion.conf` (located in `/etc/motion`). - -> **Note:** the parameters passed on this command (`%D`, `%f`, and `%t`) are called *conversion specifiers* and are described in detail in the [Motion](https://motion-project.github.io/) documentation on [ConversionSpecifiers](https://motion-project.github.io/motion_config.html#conversion_specifiers "ConversionSpecifiers"). - -1. Update the [Motion](https://motion-project.github.io/) `motion.conf` file to call **DMS3Mail** on picture save (or movie end) - - The easiest way to edit this file is to append the `on_picture_save` or `on_movie_end` command at the end of the `motion.conf` file. For example: - - ```shell - sudo sh -c "echo 'on_picture_save /usr/local/bin/go_dms3mail -pixels=%D -filename=%f -camera=%t' >> /etc/motion/motion.conf" - ``` - -1. Restart [Motion](https://motion-project.github.io/) to have the update to `motion.conf` take effect - - ```shell - sudo /etc/init.d/motion restart - ``` - - or if running with [`systemd`](https://en.wikipedia.org/wiki/Systemd)... - - ```shell - sudo service motion restart - ``` - -**DMS3Mail** will now generate and send an email whenever [Motion](https://motion-project.github.io/) generates an `on_picture_save` or `on_movie_end` command. - -## 7. Run the **DMS3** Components - -With all the **DMS3** components properly configured and installed across various server and client devices, it's now possible to run the **DMS3**. - -### Running Components as Executables - -1. On the server, run **DMS3Server** by typing `go_dms3server`. The component should now be started, and if configured, generating logging information either to the display or to a log file. - - An example of server logging output is displayed below: - - ```shell - INFO: 2017/08/27 06:51:41 lib_log.go:79: OPEN connection from: 10.10.10.16:57368 - INFO: 2017/08/27 06:51:41 lib_log.go:79: Sent motion detector state as: 0 - INFO: 2017/08/27 06:51:41 lib_log.go:79: CLOSE connection from: 10.10.10.16:57368 - INFO: 2017/08/27 06:51:52 lib_log.go:79: OPEN connection from: 10.10.10.15:33586 - INFO: 2017/08/27 06:51:54 lib_log.go:79: Sent motion detector state as: 0 - INFO: 2017/08/27 06:51:54 lib_log.go:79: CLOSE connection from: 10.10.10.15:33586 - ``` - - In this example, logging is set to the INFO level and is reporting that **DMS3Server** is sending out to all participating **DMS3Client** components a motion detector state of 0 (disabled). - -1. On each of the smart clients, run **DMS3Client** by typing `go_dms3client`. The component should now be started, and if configured, generating logging information either to the display or to a log file. - - An example of client logging output is displayed below: - - ```shell - INFO: 2017/08/28 09:18:00 lib_log.go:79: OPEN connection from: 10.10.10.5:1965 - INFO: 2017/08/28 09:18:00 lib_log.go:79: Received motion detector state as: 0 - INFO: 2017/08/28 09:18:00 lib_log.go:79: CLOSE connection from: 10.10.10.5:1965 - INFO: 2017/08/28 09:18:15 lib_log.go:79: OPEN connection from: 10.10.10.5:1965 - INFO: 2017/08/28 09:18:15 lib_log.go:79: Received motion detector state as: 0 - INFO: 2017/08/28 09:18:15 lib_log.go:79: CLOSE connection from: 10.10.10.5:1965 - ``` - - In this example, logging is set to the INFO level and is reporting that **DMS3Client** is receiving from the **DMS3Server** component a motion detector state of 0 (disabled). - -### Optional: Running Components as Services - -1. Configure the **DMS3Server** component to run as a [daemon](https://en.wikipedia.org/wiki/Daemon_(computing) "computing daemon") - - Running the **DMS3Server** component as a [`systemd`](https://en.wikipedia.org/wiki/Systemd) service is preferred, as this service can be configured to run at machine startup, recover from failures, etc. - - > As different Unix-like systems use different approaches for system service management and startup, daemon configuration is beyond the scope of the install procedure. However, the **DMS3** project does include a sample daemon file for running with [`systemd`](https://en.wikipedia.org/wiki/Systemd), called `dms3server.service`, located in the `dms3_release` folder at `dms3_release/dms3server`. - -1. Configure **DMS3Client** components to run as a [daemon](https://en.wikipedia.org/wiki/Daemon_(computing) "computing daemon") - - Running **DMS3Client** components as a [`systemd`](https://en.wikipedia.org/wiki/Systemd) service is preferred, as this service can be configured to run at machine startup, recover from failures, etc. - - > As different Unix-like systems use different approaches for system service management and startup, daemon configuration is beyond the scope of the install procedure. However, the **DMS3** project does include a sample daemon file for running with [`systemd`](https://en.wikipedia.org/wiki/Systemd), called `dms3client.service`, located in the `dms3_release` folder at `dms3_release/dms3client`. - -### Optional: View the **DMS3Dashboard** Component - -By default (as configured in `dms3dashboard.toml`), the **DMS3Dashboard** component is enabled and configured to run locally on the the **DMS3Server** component device on port 8081. To view the **DMS3Dashboard** in a web browser, go to [localhost:8081](http://localhost:8081). - -## 8. Configuration Testing & Troubleshooting - -At this point, **DMS3** should now be properly installed and configured on both the server and all smart device clients (SDCs). Once both the **DMS3Server** and **DMS3Client** are running, **DMS3** should: - -1. Watch for relevant user device proxies present on the network at a regular interval -1. Start/stop [Motion](https://motion-project.github.io/) when relevant user device proxies join/leave the network -1. Optionally, create and send an email when an event of interest is generated by [Motion](https://motion-project.github.io/) (assuming that the **DMS3Mail** component has been installed) - -### System Testing **DMS3** - -The procedure for testing **DMS3** is to add/remove a user device proxy to/from the network (i.e., enable/disable the device's networking capability), and watch (or listen, if so configured) **DMS3Server** and **DMS3Client** process motion state events. Recall that individual **DMS3** components can be configured to generate multi-level logging (INFO, ERROR, FATAL, and DEBUG) to file or [stdout](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29 "standard output"). - -### Unit Testing the **DMS3Libs** Component - -As an aid in troubleshooting issues, the **DMS3** source project tree includes a `tests` folder as part of the **DMS3Libs** component. This `tests` folder contains a number of unit tests designed to verify operation of each of the library packages used in **DMS3Libs**. - -To run a **DMS3Libs** component unit test, from the command line, change directory into the `tests` folder and choose a test to run: - -```shell -go test <*>.go -``` - -Where `<*>` is a Go test file. The unit test results will be displayed as each test is completed. - -## Appendix A: Running **DMS3** with Less Smart Device Clients (LSDCs) - -Less smart device clients (LSDCs), such as IP cameras and webcams require special consideration in **DMS3**. - -While smart device clients (SDCs) have both a camera device and a means for running a motion detection application on the same host, LSDCs typically just have a camera device, with limited or no means for processing video streams locally. - -**DMS3** resolves this limitation by allowing any **DMS3Client** to serve as an *SDC proxy* for one or more LSDCs. - -Operationally, an SDC running as a proxy for one or more LSDCs is viewed no differently than a standalone SDC. However, care must be taken to make sure that all participating LSDCs are correctly enumerated when configuring the locally-installed motion detection application on the SDC proxy. - -As an example using [Motion](https://motion-project.github.io/), the configuration file, `motion.conf`, permits multiple video devices to be managed by a single instance of [Motion](https://motion-project.github.io/). These devices can all be managed by one SDC proxy (running on a **DMS3Client** component). - -In the example file below, a portion of a `motion.conf` file is listed, showing five separate camera configurations managed by a single SDC proxy (note the last line used by the **DMS3Mail** component): - - ```shell - ... - ############################################################## - # Thread config files - One for each camera. - # Except if only one camera - You only need this config file. - # If you have more than one camera you MUST define one thread - # config file for each camera in addition to this config file. - ############################################################## - - # Remember: If you have more than one camera you must have one - # thread file for each camera. E.g. 2 cameras requires 3 files: - # This motion.conf file AND thread1.conf and thread2.conf. - # Only put the options that are unique to each camera in the - # thread config files. - - thread /home/user/security/motion_config/cam_office.conf - thread /home/user/security/motion_config/cam_livingroom.conf - thread /home/user/security/motion_config/cam_basement.conf - thread /home/user/security/motion_config/cam_garage.conf - thread /home/user/security/motion_config/cam_driveway.conf - on_picture_save /usr/local/bin/go_dms3mail -pixels=%D -filename=%f -camera=%t - ``` - -Once configured, these devices, while technically still LSDCs, are now managed through a single SDC in the context of **DMS3**. - -> **Note:** it's possible to install both a **DMS3Client** component and a **DMS3Server** component on the same machine. In this configuration, the host serves as a **DMS3** server (**DMS3Server**) for a client (**DMS3Client**) that happens to be running locally (localhost), which in turn, can serve as an SDC proxy for any number of remote LSDCs. diff --git a/QUICK_INSTALL.md b/QUICK_INSTALL.md deleted file mode 100644 index e1f4415..0000000 --- a/QUICK_INSTALL.md +++ /dev/null @@ -1,270 +0,0 @@ -# Distributed Motion Surveillance Security System (DMS3) Quick Install - -This procedure describes how to use the `dms3build` build process found in this project to install the **Distributed Motion Surveillance Security System (DMS3)**. - -For details on how to manually install **Distributed Motion Surveillance Security System (DMS3)**, see the [Distributed Motion Surveillance Security System (DMS3) Manual Installation](https://github.com/richbl/go-distributed-motion-s3/blob/master/MANUAL_INSTALL.md) documentation. This document also provides much greater technical depth in describing the **DMS3** installation process. - -## Installation Overview - -The installation of **DMS3** is comprised of two steps: - -1. The installation and configuration of **DMS3** components on participating hardware devices: - - | Component | Install Location | Required? | - | :------------- | :------------- | :------------- | - | DMS3Server | server | Yes | - | DMS3Client | smart device clients (SDCs) | Yes | - | DMS3Libs | server, SDCs | Yes | - | DMS3Dashboard | server | Yes (but can be disabled) | - | DMS3Mail | SDCs | Optional(*) | - - > (*) if using the [Motion](https://motion-project.github.io/) motion detection application, the **DMS3Mail** component can be installed on the SDC to manage real-time email notification of surveillance events - -1. The installation and configuration of a motion detection application, such as [Motion](https://motion-project.github.io/ "Motion") or the [OpenCV](http://opencv.org/ "Open Source Computer Vision Library") Library - -## 1. Download the **DMS3** Project Release - -1. Download the appropriate release file from [the DMS3 release repository](https://github.com/richbl/go-distributed-motion-s3/releases) and decompress into a temporary folder - - The **DMS3** release contains a folder called `dms3_release` with all the necessary platform-specific [Go](https://golang.org/ "Go") binary executables, services, configuration, and media files. During installation, these files will be redistributed to either a **DMS3Server** component device or any number of **DMS3Client** component devices (SDCs). - - Note that **DMS3** releases contains executables for the following: - - - Linux AMD64 - - Linux ARM6 (e.g., Raspberry Pi A, A+, B, B+, Zero) - - Linux ARM7 (e.g., Raspberry Pi 2, 3) - - **DMS3** components must be compiled for the operating system (e.g., Linux) and CPU architecture (e.g., AMD64) of the hardware device on which the component will be installed. If the OS and architecture are not available in an official **DMS3** release, clone/download the **DMS3** project tree, configure a platform and compile as appropriate. For details on [Go](https://golang.org/ "Go") compiler support, see the [Go support for various architectures and OS platforms](https://golang.org/doc/install/source#environment "Go Support"). - - The folder structure of a typical **DMS3** release is as follows: - -```shell - dms3_release - ├── dms3build - │   └── dms3build.toml - ├── dms3client - │   ├── dms3client.service - │   └── dms3client.toml - ├── dms3dashboard - │   ├── assets - │   │   ├── css - │   │   │   ├── bootstrap.min.css - │   │   │   ├── icomoon-icons.css - │   │   │   └── paper-dashboard.css - │   │   ├── fonts - │   │   │   ├── icomoon.eot - │   │   │   ├── icomoon.svg - │   │   │   ├── icomoon.ttf - │   │   │   └── icomoon.woff - │   │   └── img - │   │   └── favicon.ico - │   ├── dashboard.html - │   └── dms3dashboard.toml - ├── dms3libs - │   └── dms3libs.toml - ├── dms3mail - │   └── dms3mail.toml - ├── dms3server - │   ├── dms3server.service - │   ├── dms3server.toml - │   └── media - │   ├── motion_start.wav - │   └── motion_stop.wav - ├── linux_amd64 - │   ├── dms3client_remote_installer - │   ├── dms3server_remote_installer - │   ├── go_dms3client - │   ├── go_dms3mail - │   ├── go_dms3server - │   └── install_dms3 - ├── linux_arm6 - │   ├── dms3client_remote_installer - │   ├── dms3server_remote_installer - │   ├── go_dms3client - │   ├── go_dms3mail - │   ├── go_dms3server - │   └── install_dms3 - └── linux_arm7 - ├── dms3client_remote_installer - ├── dms3server_remote_installer - ├── go_dms3client - ├── go_dms3mail - ├── go_dms3server - └── install_dms3 - -``` - -## 2. Configure the **DMS3** Components - -All **DMS3** components are configured through an associated text-based configuration file called a TOML ([Tom's Obvious, Minimal Language](https://github.com/toml-lang/toml)) file, and a common file extension, `*.toml`. This file is very minimal in format, but well-documented with many defaults preset, so should be generally self-explanatory. The table below identifies the TOML file with the component: - - | Component | TOML File Location | - | :------------- | :------------- | - | DMS3Server | dms3_release/dms3server/dms3server.toml | - | DMS3Client | dms3_release/dms3client/dms3client.toml | - | DMS3Libs | dms3_release/dms3libs/dms3libs.toml | - | DMS3Dashboard | dms3_release/dms3dashboard/dms3dashboard.toml | - | DMS3Mail | dms3_release/dms3mail/dms3mail.toml | - -> For details about the configuration options available in each TOML file, see the *Configure DMS3 Components* section in [Distributed Motion Surveillance Security System (DMS3) Manual Installation](https://github.com/richbl/go-distributed-motion-s3/blob/master/INSTALL.md). - -The one TOML file not directly associated with a specific **DMS3** component is the `dms3build.toml` file, which is responsible for configuring the **DMS3** build process. Details for configuring this special TOML file are presented below. - -## 3. Install the **DMS3** Components - -1. Configure the Installer - - The `dms3_release` folder includes a folder called `dms3build` which contains a file, `dms3build.toml`, used for enumerating and configuring **DMS3** components across a network. All participating **DMS3** components must be represented in this configuration file. - - An example of a **DMS3Server** component device: - - ```toml - [Servers.0] - User = "richbl" - DeviceName = "main.local" - SSHPassword = "" # using SSH certificate - RemoteAdminPassword = "PASSWORD" - Port = 22 - Platform = "linuxAMD64" - ``` - - An example of a **DMS3Client** smart client device (SDC): - - ```toml - [Clients.0] - User = "pi" - DeviceName = "picam-alpha.local" - SSHPassword = "" # using SSH certificate - RemoteAdminPassword = "PASSWORD" - Port = 22 - Platform = "linuxArm7" - ``` - - A device configuration must be filled out for each device that will participate in the **DMS3** network environment. - - >**Note:** the `dms3build` process uses the SSH protocol to perform remote actions on these device client platforms. **Be sure SSH is made available and properly configured on these devices** - -1. Run the Installer - - The `dms3build` installer does the following: - 1. Copies all platform-specific binaries, services, configuration, and media files to the designated device platform (the `dms3_release` folder) - 1. Copies a remote installer script to that device platform - 1. Runs the remote installer script, which in turn, redistributes **DMS3** component files into their respective default locations on that device - 1. Upon successful completion, deletes the remote installer script - - To install all configured **DMS3** components, from the proper release platform folder (e.g., `/linux_amd64`), run `install_dms3` (i.e., `./install_dms3`) - - The `dms3build` installer will display installation progress on all device platforms. On completion, these device platforms will be properly configured to run **DMS3** components. - -## 4. Confirm the Installation of a Motion Detection Application on All SDCs - -Without an operational motion detection application running on the configured **DMS3Client** components, **DMS3** really doesn't have much to do, though **DMS3Server** will obligingly send enable/disable messages to all listening **DMS3Client** components based on its user proxy configuration rules. - -1. Confirm the installation of a motion detection application on all smart device clients (SDCs), such as a desktop computer, or Raspberry Pi or similar single board computer (SBC), all with an operational video camera device - -1. If using the [Motion](https://motion-project.github.io/ "Motion") motion detection application, configure [Motion](https://motion-project.github.io/) to run as a daemon - - For proper operation with **DMS3**, [Motion](https://motion-project.github.io/) must be set to run in daemon mode (which permits [Motion](https://motion-project.github.io/) to run as a background process). This is achieved through an edit made to the `motion.conf` file located in the [Motion](https://motion-project.github.io/) folder (e.g., `/etc/motion`). - - In the section called Daemon, set the `daemon` variable to `on` as noted below: - - ```shell - ############################################################ - # Daemon - ############################################################ - - # Start in daemon (background) mode and release terminal (default: off) - daemon on - ``` - -## 5. Optional: Integrate **DMS3Mail** with [Motion](https://motion-project.github.io/) on the Device Client - -**DMS3Mail** is a stand-alone client-side component responsible for generating and sending an email whenever a valid motion event is triggered in [Motion](https://motion-project.github.io/). The **DMS3Mail** component is called by [Motion](https://motion-project.github.io/) whenever the [*on_picture_save*](https://motion-project.github.io/motion_config.html#on_picture_save "on_picture_save command") and the [on_movie_end](https://motion-project.github.io/motion_config.html#on_movie_end "on_movie_end command") commands (called [hooks](http://en.wikipedia.org/wiki/Hooking "Hooking")) are fired during a motion event. - -> Note that **DMS3Mail** runs independently from, and has no dependencies upon, **DMS3Client** (or **DMS3Server**). It can be run standalone with [Motion](https://motion-project.github.io/), apart from **DMS3** entirely. - -The syntax for these [Motion](https://motion-project.github.io/) commands are: - -```shell - -pixels=%D -filename=%f -camera=%t -``` - -These commands are saved in the [Motion](https://motion-project.github.io/) configuration file called `motion.conf` (located in `/etc/motion`). - -> **Note:** the parameters passed on this command (`%D`, `%f`, and `%t`) are called *conversion specifiers* and are described in detail in the [Motion](https://motion-project.github.io/) documentation on [ConversionSpecifiers](https://motion-project.github.io/motion_config.html#conversion_specifiers "ConversionSpecifiers"). - -1. Update the [Motion](https://motion-project.github.io/) `motion.conf` file to call **DMS3Mail** on picture save (or movie end) - - The easiest way to edit this file is to append the `on_picture_save` or `on_movie_end` command at the end of the `motion.conf` file. For example: - - ```shell - sudo sh -c "echo 'on_picture_save /usr/local/bin/go_dms3mail -pixels=%D -filename=%f -camera=%t' >> /etc/motion/motion.conf" - ``` - -1. Restart [Motion](https://motion-project.github.io/) to have the update to `motion.conf` take effect - - ```shell - sudo /etc/init.d/motion restart - ``` - - or if running with [`systemd`](https://en.wikipedia.org/wiki/Systemd)... - - ```shell - sudo service motion restart - ``` - -**DMS3Mail** will now generate and send an email whenever [Motion](https://motion-project.github.io/) generates an `on_picture_save` (or `on_movie_end`) command. - -## 6. Run the **DMS3** Components - -With all the **DMS3** components properly configured and installed across various server and client devices, it's now possible to run the **DMS3**. - -### Running Components as Executables - -1. On the server, run **DMS3Server** by typing `go_dms3server`. The component should now be started, and if configured, generating logging information either to the display or to a log file. - - An example of server logging output is displayed below: - - ```shell - INFO: 2017/08/27 06:51:41 lib_log.go:79: OPEN connection from: 10.10.10.16:57368 - INFO: 2017/08/27 06:51:41 lib_log.go:79: Sent motion detector state as: 0 - INFO: 2017/08/27 06:51:41 lib_log.go:79: CLOSE connection from: 10.10.10.16:57368 - INFO: 2017/08/27 06:51:52 lib_log.go:79: OPEN connection from: 10.10.10.15:33586 - INFO: 2017/08/27 06:51:54 lib_log.go:79: Sent motion detector state as: 0 - INFO: 2017/08/27 06:51:54 lib_log.go:79: CLOSE connection from: 10.10.10.15:33586 - ``` - - In this example, logging is set to the INFO level and is reporting that **DMS3Server** is sending out to all participating **DMS3Client** components a motion detector state of 0 (disabled). - -1. On each of the smart clients, run **DMS3Client** by typing `go_dms3client`. The component should now be started, and if configured, generating logging information either to the display or to a log file. - - An example of client logging output is displayed below: - - ```shell - INFO: 2017/08/28 09:18:00 lib_log.go:79: OPEN connection from: 10.10.10.5:1965 - INFO: 2017/08/28 09:18:00 lib_log.go:79: Received motion detector state as: 0 - INFO: 2017/08/28 09:18:00 lib_log.go:79: CLOSE connection from: 10.10.10.5:1965 - INFO: 2017/08/28 09:18:15 lib_log.go:79: OPEN connection from: 10.10.10.5:1965 - INFO: 2017/08/28 09:18:15 lib_log.go:79: Received motion detector state as: 0 - INFO: 2017/08/28 09:18:15 lib_log.go:79: CLOSE connection from: 10.10.10.5:1965 - ``` - - In this example, logging is set to the INFO level and is reporting that **DMS3Client** is receiving from the **DMS3Server** component a motion detector state of 0 (disabled). - -### Optional: Running Components as Services - -1. Configure the **DMS3Server** component to run as a [daemon](https://en.wikipedia.org/wiki/Daemon_(computing) "computing daemon") - - Running the **DMS3Server** component as a [`systemd`](https://en.wikipedia.org/wiki/Systemd) service is preferred, as this service can be configured to run at machine startup, recover from failures, etc. - - > As different Unix-like systems use different approaches for system service management and startup, daemon configuration is beyond the scope of the install procedure. However, the **DMS3** project does include a sample daemon file for running with [`systemd`](https://en.wikipedia.org/wiki/Systemd), called `dms3server.service`, located in the `dms3_release` folder at `dms3_release/dms3server`. - -1. Configure **DMS3Client** components to run as a [daemon](https://en.wikipedia.org/wiki/Daemon_(computing) "computing daemon") - - Running **DMS3Client** components as a [`systemd`](https://en.wikipedia.org/wiki/Systemd) service is preferred, as this service can be configured to run at machine startup, recover from failures, etc. - - > As different Unix-like systems use different approaches for system service management and startup, daemon configuration is beyond the scope of the install procedure. However, the **DMS3** project does include a sample daemon file for running with [`systemd`](https://en.wikipedia.org/wiki/Systemd), called `dms3client.service`, located in the `dms3_release` folder at `dms3_release/dms3client`. - -### Optional: View the **DMS3Dashboard** Component - -By default (as configured in `dms3dashboard.toml`), the **DMS3Dashboard** component is enabled and configured to run locally on the the **DMS3Server** component device on port 8081. To view the **DMS3Dashboard** in a web browser, go to [localhost:8081](http://localhost:8081). diff --git a/README.md b/README.md index 1480c5d..0a4db20 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,40 @@ +# Distributed Motion Surveillance Security System (DMS3) + [![Go Report Card](https://goreportcard.com/badge/github.com/richbl/go-distributed-motion-s3)](https://goreportcard.com/report/github.com/richbl/go-distributed-motion-s3) [![codebeat badge](https://codebeat.co/badges/155e9293-7023-4956-81f5-b3cde7b93842)](https://codebeat.co/projects/github-com-richbl-go-distributed-motion-s3-master) +![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/richbl/go-distributed-motion-s3?include_prereleases) -# Distributed Motion Surveillance Security System (DMS3) +## New for Release 1.4.0 + +Much has changed over the past 4+ years since the 1.3.1 stable release of **DMS3**, so this release has focused on upgrades and improvements to make the **DMS3** surveillance security system that so many people have relied upon even more efficient, stable, and secure. + +### **DMS3Mail** + +

+ DMS3Mail Event +

+ +- The **DMS3Mail** component gets a significant makeover in response to ongoing changes in the use of advanced progressive HTML5 email templates developed to work with myriad end-user email applications. Upgrades to **DMS3Mail** include: + - **NEW!** A much-anticipated and fully configurable HTML5 email template, based on the very popular [Cerberus responsive email patterns](https://github.com/TedGoas/Cerberus). For use in **DMS3**, we integrated the [Go HTML/template package](https://pkg.go.dev/html/template) into the Cerberus fluid template, very similar to what we did when creating the **DMS3Dashboard** component. This new responsive email template now presents a more complete email message to the end user, with the following functionality: + - **NEW!** Larger image attachments are now integrated directly into the email body (versus as an attachment) + - **NEW!** More complete metrics now presented in the email for each security event, including the hostname of the **DMS3 Client** component sourcing the event. + - **NEW!** The percentage of changes (in pixels) is now provided, thanks to a new *GetImageDimensions()* routine that provides image details as **DMS3Mail** processes the security event in real-time. + - As part of this new progressive email template, email "dark mode" is now handled automatically, making it easier to view email on disparate mobile platforms -## NEW! for Release 1.3.0 - **DMS3Dashboard** +### **DMS3Dashboard** -![dms3_dashboard](https://user-images.githubusercontent.com/10182110/33868591-bb0e2608-deb8-11e7-802b-3f30a71683fd.png -) +

+ DMS3Dashboard Display +

-Ever wonder if your surveillance cameras are operational, in need of updates, or even a reboot? The new **DMS3Dashboard** component can be enabled to run on a **DMS3Server** component and provide regularly-updated information for all **DMS3Client** components with device metrics including: +Ever wonder if your surveillance cameras are operational, in need of updates, or even a reboot? The **DMS3Dashboard** component can be enabled to run on a **DMS3Server** and provide regularly-updated information of all **DMS3Client** components with device metrics that include: - Hostname - Hardware platform and operating system - Kernel version -- Current **DMS3** uptime -- Count of surveillance events generated by that component (if applicable) +- Current **DMS3Server** and all reporting **DMS3Client** components' uptime +- Count of **DMS3Clients** reporting to the **DMS3Server** +- Count of surveillance events generated by **DMS3Clients** components - Date/time (ISO 8601) the component last reported to the **DMS3Server** Additionally, **DMS3Dashboard** provides a quick visual health check of all **DMS3Client** components, using color-sensitive component icons, where: @@ -23,145 +43,167 @@ Additionally, **DMS3Dashboard** provides a quick visual health check - **Yellow**: a **DMS3Client** is late in reporting, exceeding its configured reporting interval - **Red**: a **DMS3Client** has not reported to the server, and requires attention -> In the image above, five **DMS3** device components are displayed in the **DMS3Dashboard**: one **DMS3Server** (listed first), and four **DMS3Client** device components. One **DMS3Client**--*picam-alpha*--is late in reporting to the server, and so its icon has turned yellow to warn the user of its potentially failing status - -The new **DMS3Dashboard** component is written using [Go's HTML templating package](https://golang.org/pkg/html/template/), making it very easy to integrate new or existing HTML template themes into the component. The template used by **DMS3Dashboard** is based largely on the following resources: +The **DMS3Dashboard** component is written using [Go's HTML templating package](https://pkg.go.dev/html/template), making it very easy to integrate new or existing HTML template themes into the component. The template used by **DMS3Dashboard** is based largely on the following resources: - [Creative Tim's Paper Dashboard Theme](https://github.com/creativetimofficial/paper-dashboard) - Fonts provided by [Icomoon](https://icomoon.io/) -> Note that the **DMS3Dashboard** version of the Paper Dashboard design is heavily modified (primarily reduced in size and resources, and JS removed), and the original [Themify](https://themify.me/themify-icons) fonts replaced with [Icomoon](https://icomoon.io/) fonts, among other design changes. To demo the unadulternated Paper Dashboard template in action, [see Creative Tim's excellent live preview here](http://demos.creative-tim.com/paper-dashboard/dashboard.html). - -## 1. What Is DMS3? - -![dms3_topology](https://user-images.githubusercontent.com/10182110/28693283-c3c11518-72d8-11e7-8d41-f167cb8f3b13.png) - -**Distributed Motion Surveillance Security System (DMS3)** is a [Go](https://golang.org/ "Go")-based application that integrates third-party open-source motion detection applications (e.g., the [Motion](https://motion-project.github.io/ "Motion") motion detection software package, or [OpenCV](http://opencv.org/ "OpenCV"), the Open Source Computer Vision Library) into a distributed surveillance system that: - -- Senses when someone is "at home" and when someone is "not home" and **automatically enables or disables the surveillance system** -- Distributes video stream processing, reporting, and user notification to capable "smart" device clients (e.g., a Raspberry Pi) which: - - Minimizes network congestion, *particularly during high-bandwidth surveillance events of interest* - - Better utilizes smart device client endpoint CPU processing power: keeping stream processing "on-board" and localized +New for this release are the following additional configuration options for **DMS3Dashboard**: + +- **NEW!** Independently configurable client icon status option timeouts (warning, danger, missing) visually provide a status of a **DMS3Client** health in real-time +- **NEW!** Option to make **DMS3Server** always first in the set of **DMS3Client** devices displayed in the dashboard +- **NEW!** Option to alphabetically sort **DMS3** devices displayed in the dashboard +- **NEW!** Reporting a more comprehensive--and now dynamically updated--list of **DMS3** device attributes, including: + - Operating system name and version release (*e.g.,* Raspbian GNU/Linux 10) + - Hardware platform (*e.g.,* Linux ARM7l) + - Kernel release (*e.g.,* 5.10.63-v7+) +- **NEW!** Various additional upgrades to the dashboard HTML template, including revisions to the template display, and updates to use the new **DMS3** logo in the template header + +### **DMS3Server** & **DMS3Client** + +- Both of these **DMS3** components received a significant upgrade that includes: + - A revision to the **DMS3** component codebase, moving from [Go 1.8 to Go 1.17](https://go.dev/doc/devel/release), bringing with this language release update numerous new low-level packages, platform performance optimizations, and security enhancements + - The addition of the ARM8 platform type (great news for Raspberry Pi and related SBC users), automatically incorporated into our native **DMS3Build** process + - As part of the **DMS3Build** process, the remote installers have been rewritten to abstract away specific Linux dependencies + - Revised overall project structure to reflect idiomatic Go principles + - Commands now organized into a `cmd` folder, while configuration files are now managed in a `config` folder + - Project moved from use of the `gocode` process to using `gopls` + - Project migration over to the use of [Go modules](https://go.dev/ref/mod) + - System-level service (daemon) calls are now abstracted away to work on across a broader array of Unix-like operating systems + - **DMS3Server** listening port moved from the previous registered port range into the more appropriate dynamic/private range + - All [TOML](https://github.com/toml-lang/toml) configuration files revved from 0.4.0 and validated ([tomlv](https://github.com/BurntSushi/toml/tree/master/cmd/tomlv)) to 1.0.0 + +## What Is **DMS3**? + +

+ DMS3 Topology +

+ +> If you appreciate isometric drawings, please check out our [isometric-icons project, located here](https://github.com/richbl/isometric-icons). + +**Distributed Motion Surveillance Security System (DMS3)** is a [Go-based](https://golang.org/ "Go") application that integrates third-party open-source motion detection applications (*e.g.*, the [Motion](https://motion-project.github.io/ "Motion") motion detection software package, or [OpenCV](http://opencv.org/ "OpenCV"), the Open Source Computer Vision Library) into a distributed motion surveillance system that: + +- Senses when someone is "at home" and when someone is "not at home" and **automatically enables or disables the surveillance system** +- Through the **DMS3Server**, coordinates video stream processing, reporting, and user notification to capable "smart" device clients, running the **DMS3Client** component (*e.g.*, a Raspberry Pi) which: + - Greatly minimizes network congestion, *particularly during high-bandwidth surveillance events of interest* + - Better utilizes smart device client CPU/GPU processing power: keeping stream processing "on-board" and distributed around the network +- Optionally, **DMS3Clients** can report events of interest via email using the available **DMS3Mail** component +- Optionally, the **DMS3Server** can display the current state of all reporting **DMS3Clients** visually through the use of the **DMS3Dashboard** component - Works cooperatively with legacy "less smart" device clients such as IP cameras (wired or WiFi), webcams, and other USB camera devices -## 2. DMS3 Features - -Here's a list of some of the more outstanding features of **DMS3**: - -### Motion Detection Application Support - -While **DMS3** is primarily responsible for sensing user proxies and determining when to enable or disable the surveillance system. *It alone does not manage the processing of video stream data*. That complex, real-time task is left to motion detection libraries/applications which can be integrated into **DMS3**. - -- Support for the [Motion](https://motion-project.github.io/ "Motion") motion detector software package - - - Movement detection support of video devices. See [this list](https://github.com/Motion-Project/motion/wiki/Supported-hardware "Device Compatibility") for video device compatibility. Note that **DMS3** was developed and tested using smart device clients running [Motion](https://motion-project.github.io/ "Motion") with native camera support (e.g., a Raspberry Pi with an on-board camera module) - -- Support for the [OpenCV](http://opencv.org/ "Open Source Computer Vision Library") Library [planned] +## **DMS3** Features - - [OpenCV](http://opencv.org/ "Open Source Computer Vision Library") support is highly anticipated, but still experimental, though the current codebase cleanly abstracts away any specific motion detection application dependencies, so it should be a very straightforward integration +### **DMS3Client**, **DMS3Server**, and **DMS3Dashboard** Features -### **DMS3Client** & **DMS3Server** Features - -- **NEW!** support for the new **DMS3Dashboard** component, allowing for the easy, visual monitoring of all **DMS3Client** devices managed by a **DMS3Server** component +- Support for the **DMS3Dashboard** component, allowing for the easy, visual monitoring of all **DMS3Client** devices managed by a **DMS3Server** component - Mobile first, responsive, web-based design - - Uses [Go's HTML templating package](https://golang.org/pkg/html/template/) to simplify HTML integration + - Uses [Go's HTML templating package](https://pkg.go.dev/html/template) to simplify Go/HTML integration - Easily integrate 3rd-party configurable HTML website templates -- Automated starting/stopping of any number of motion detection applications installed on smart device clients (e.g., the [Motion](https://motion-project.github.io/ "Motion") motion detector software package) based on the presence/absence of user proxy devices +- Automated starting/stopping of any number of motion detection applications installed on smart device clients (*e.g.*, the [Motion](https://motion-project.github.io/ "Motion") motion detector software package) based on the presence/absence of user proxy devices - *Always On* feature starts/stops the motion detection application based on time-of-day (*e.g*., can enable video surveillance during nighttime or specific holiday hours) -- Optionally play audio file(s) on surveillance system enable/disable +- Device clients can be custom-configured to process and respond to surveillance events independently and uniquely (*e.g.*, an outdoor IR camera device only sends email during nighttime hours) +- Optionally play separate audio file(s) on surveillance system enable and disable - Configurable event logging - INFO, ERROR, FATAL, and DEBUG log levels - - Persist logs to file or [stdout](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29 "standard output") -- Multiple user proxy device support (can sense device presence/absence from a list of devices) + - Persist logs to file or send to STDOUT (terminal) - [MAC](http://en.wikipedia.org/wiki/MAC_address "MAC address") (Layer 2) address sensing - IPv4 protocol support - IPv6 protocol support [planned] - Bluetooth user proxy sensing (using RSSI, L2CAP, or similar) [planned] -- Device clients can be custom-configured to process and respond to surveillance event data independently and uniquely (e.g., an outdoor IR camera device only sends email during nighttime hours) - -### Support for "Smart" and "Less Smart" Device Clients - -**DMS3** is designed to utilize intelligent IoT sensing devices, called **Smart Device Clients (SDCs)**, while still supporting less intelligent, single-purpose devices, called **Less Smart Device Clients (LSDCs)**. - -- **DMS3 Smart Device Clients (SDCs)** are hardware devices capable of processing local video streams for motion detection on-board, with dedicated hardware. Most computers and smaller **single board computers (IoT SBCs)** would be classed as smart device clients, including: - - Raspberry PIs (**DMS3** was tested with the RaspPi Model 2, Model 3, and Pi Zero W) with a configured on-board camera - - Any IoT single board computer (SBC) capable of running a Unix-like operating system - - Personal computers with a camera and wired or wireless (WiFi) connectivity - -- **DMS3 Less Smart Device Clients (LSDCs)** are hardware devices--typically purpose-built--unable to locally process motion detection in video streams. These devices generate raw real-time video data, which is then consumed and processed by an external device(s), oftentimes across the network. Some examples of LSDCs include: - - - IP cameras (e.g., the [Nest Cam](https://nest.com/cameras/ "Google Nest")), either wired or wireless (WiFi) - - Webcams, typically using USB connections and run from a desktop computer ### **DMS3Mail** Features - Developed for use exclusively with [Motion](https://motion-project.github.io/ "Motion"), **DMS3Mail** is an automated, real-time email notification service triggered by [Motion](https://motion-project.github.io/ "Motion")-generated detection events - - Fully configurable email message subject, body, *etc.* + - Fully configurable email message subject, body, *etc.* using the excellent [Cerberus](https://github.com/TedGoas/Cerberus) responsive HTML email template - Optionally attach an event image or video to an email message - - SMTP-support for compatibility with most webmail services (e.g., [Gmail](http://gmail.com "Google Gmail")) + - SMTP-support for compatibility with most web-mail services (*e.g.*, [Gmail](http://gmail.com "Google Gmail")) - Configurable event logging - INFO, ERROR, FATAL, and DEBUG log levels - - Persist logs to file or [stdout](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_.28stdout.29 "standard output") + - Persist logs to file or send to STDOUT (terminal) + +### Motion Detection Application Support + +While **DMS3** is primarily responsible for monitoring user proxies and determining when to enable or disable the surveillance system, *it alone does not manage the processing of video stream data*. That complex real-time task is left to motion detection libraries/applications which can be integrated directly into **DMS3**. + +- Support for the [Motion](https://motion-project.github.io/ "Motion") motion detector software package -## 3. DMS3 Use Cases + - Movement detection support of video devices. See [this list](https://github.com/Motion-Project/motion/wiki/Supported-hardware "Device Compatibility") for video device compatibility. Note that **DMS3** was developed and tested using smart device clients running [Motion](https://motion-project.github.io/ "Motion") with native camera support (*e.g.*, a Raspberry Pi with an on-board camera module) + +- Support for the [OpenCV](http://opencv.org/ "Open Source Computer Vision Library") Library [planned] + + - [OpenCV](http://opencv.org/ "Open Source Computer Vision Library") support is highly anticipated, but still experimental, though the current **DMS3** codebase cleanly abstracts away any specific motion detection application dependencies so it is anticipated to be a very straightforward integration + +### Support for "Smart" and "Less Smart" Device Clients + +**DMS3** is designed to utilize intelligent IoT devices, called **Smart Device Clients (SDCs)**, while still supporting less intelligent, single-purpose devices, called **Less Smart Device Clients (LSDCs)**. + +- **DMS3 Smart Device Clients (SDCs)** are hardware devices capable of processing local video streams for motion detection on-board, with dedicated hardware. Most computers and smaller single board computers (IoT SBCs) would be classed as smart device clients, including: + - Raspberry PIs (**DMS3** was tested with the RaspPi Model 2, Model 3, and Pi Zero W) with a configured on-board camera module + - Any IoT single board computer (SBC) capable of running a Unix-like operating system + - Personal computers with a camera and wired or wireless (WiFi) connectivity + +- **DMS3 Less Smart Device Clients (LSDCs)** are hardware devices--typically purpose-built--unable to process motion detection video streams locally. These devices usually generate raw real-time video data, which is then sent, consumed and processed by an external device(s), oftentimes wirelessly across a network. Some examples of LSDCs include: + + - IP cameras (*e.g.*, the [Nest Cam](https://nest.com/cameras/ "Google Nest")), either wired or wireless + - Webcams, typically using USB connections and run from a laptop or desktop computer + +## **DMS3** Use Cases ### "Leaving Home, Coming Home" -At its core, **DMS3** sensing relies on the concept of a *user proxy*. In this context, *a user proxy is any device representing a user that can be sensed on a home network*. A smartphone is an excellent user proxy, assuming that a user's smartphone is active on a home network when the user is "at home," and leaves the network when the user "leaves home." +At its core, **DMS3** sensing relies on the concept of a *user proxy*. In this context, *a user proxy is any device representing a user that can be sensed on a home network*. A smartphone is an excellent user proxy, assuming that a user's smartphone is active on the home network when the user is "at home," and drops from the network when the user leaves and is then "not at home." -This concept can extend to multiple user proxies, making it possible for **DMS3** to keep a surveillance system disabled until everyone in a family has left home: once the last registered user proxy is no longer sensed on the home network, **DMS3** automatically enables the surveillance system. +This concept can extend to multiple user proxies, making it possible for **DMS3** to keep a surveillance system disabled until everyone in an entire family has left home: once the last registered user proxy is no longer sensed on the home network, **DMS3** automatically enables the surveillance system. -The reverse is true as well: **DMS3** will keep a surveillance system enabled only until the first user proxy is seen on the home network (e.g., someone pulling into the driveway), at which time **DMS3** will automatically disable the surveillance system. +The reverse is true as well: **DMS3** will keep the surveillance system enabled only until the first user proxy is seen on the home network (*e.g.,* someone pulling into the driveway), at which time **DMS3** will automatically disable the surveillance system. ### "Nighttime Surveillance" -In addition to sensing user proxies, **DMS3** can be configured to keep a surveillance system enabled over specific periods of time. Called *Always On*, this **DMS3** feature works as an override for user proxies: regardless of whether **DMS3** senses a user proxy on the network, as long as the time-of-day policy is met, **DMS3** will enable the surveillance system. +In addition to sensing user proxies, **DMS3** can be configured to keep a surveillance system enabled for a specific periods of time. Called *Always On*, this **DMS3** feature works as an override for sensing user proxies: regardless of whether **DMS3** senses a user proxy on the network, as long as the time-of-day policy is met, **DMS3** will enable the surveillance system. -This feature is particularly useful for nighttime surveillance, when users may be asleep and/or smartphones may be turned off. For example, **DMS3** can be configured to turn a surveillance system on at 2330, and stay on until 0500 the next morning. During this time, **DMS3** will remain operational and report surveillance events as they occur. +This feature is particularly useful for nighttime surveillance, when users may be asleep and/or smartphones may be turned off. For example, **DMS3** can be configured to turn a surveillance system on at 2330, and stay on until 0500 the next morning. During this time, **DMS3** will remain operational and monitor (and report) surveillance events as they occur. -## 4. DMS3 Components +## **DMS3** Components **DMS3** is organized into the following application components: -- **DMS3Server**: integrated server-side system services that determine whether to enable/disable the surveillance system, and regularly update participating **DMS3** device clients of that surveillance state -- **DMS3Client**: client-side endpoint services that start/stop the locally-installed motion detection application (e.g., [Motion](https://motion-project.github.io/ "Motion")). Any number of **DMS3Client** clients can exist as part of the **DMS3** surveillance system +- **DMS3Server**: integrated server-side system services that determine whether to enable/disable the surveillance system, and regularly notifies participating **DMS3** device clients of that surveillance state. A **DMS3Server** is typically instantiated on a headless server or home desktop computer +- **DMS3Client**: client-side endpoint services that start/stop the locally-installed motion detection application (*e.g.*, [Motion](https://motion-project.github.io/ "Motion")). Any number of **DMS3Client** clients can exist as part of the **DMS3** surveillance system. **DMS3Clients** are typically installed on IoT hardware (*e.g.,* Raspberry PI or similar SBC devices) +- **DMS3Dashboard**: an optional component that permits for the visual display and real-time status of **DMS3Client** components through a web browser - **DMS3Libs**: a set of related shared libraries used for managing **DMS3** client-server services including low-level system and networking commands, system logging, and unit testing -- **DMS3Dashboard**: an optionally-enabled component that permits for the visual inspection of **DMS3** component metrics through a web browser Optional for smart device clients configured to use the [Motion](https://motion-project.github.io/ "Motion") motion detection application: -- **DMS3Mail**: a separate configurable **DMS3** component for generating and sending an email with videos/images whenever a client running [Motion](https://motion-project.github.io/ "Motion") generates a significant motion-related hook events (such as [`on_picture_save`](https://htmlpreview.github.io/?https://github.com/Motion-Projeloggingct/motion/blob/master/motion_guide.html#on_picture_save "Motion on_picture_save") and [`on_movie_end`](https://htmlpreview.github.io/?https://github.com/Motion-Project/motion/blob/master/motion_guide.html#on_movie_end "Motion on_movie_end")) +- **DMS3Mail**: a separate, configurable **DMS3** component for generating and sending an email in real-time whenever a client running [Motion](https://motion-project.github.io/ "Motion") generates a significant security event -## 5.0 DMS3 Architecture +## **DMS3** Architecture -**DMS3** is patterned after a [client server model](https://en.wikipedia.org/wiki/Client%E2%80%93server_model "client server model"), where **DMS3Server** is centrally responsible for the logic of enabling/disabling the video surveillance system, while each participating smart device client is responsible for starting/stopping the locally-installed motion detection application. For less smart device clients, the processing of video stream data is passed over the wire to the server for processing and eventual system response and/or user notification. +**DMS3** is patterned after a [client server model](https://en.wikipedia.org/wiki/Client%E2%80%93server_model "client server model"), where the **DMS3Server** component is centrally responsible for the logic of enabling/disabling the video surveillance system, while each participating smart device client (SDC), through the use of the **DMS3Client** component, is responsible for starting/stopping the locally-installed motion detection application. For "less smart" device clients (LSDCs), the processing of video stream data is passed over the wire to the server for processing and eventual system response and/or user notification. -In the example presented at the start of this document, one IP camera device, one IoT SBC device (a Raspberry Pi), and one webcam device are managed through **DMS3Server** (using the [TCP protocol](https://en.wikipedia.org/wiki/Transmission_Control_Protocol "TCP protocol")). **DMS3Server** determines when to enable/disable the surveillance system, and notifies each participating device client. Since the Raspberry Pi can be configured to run a local instance of a motion detection application (it's a smart device client), **actual video stream processing, imaging, and eventual reporting is done locally**. +In the example presented at the start of this document, one IP camera device, one IoT SBC device (a Raspberry Pi), and one webcam device are managed through the **DMS3Server** component (using the [TCP protocol](https://en.wikipedia.org/wiki/Transmission_Control_Protocol "TCP protocol")). The **DMS3Server** determines when to enable/disable the surveillance system, and notifies each participating device client running their own local instance of the **DMS3Client** component. Since the Raspberry Pi can be configured to run a local instance of a motion detection application, actual video stream processing, imaging, and eventual reporting is all done locally, greatly limiting network congestion. -The webcam device and the IP camera device--both less smart device clients, and incapable of on-board stream processing--must pass raw stream data along to a device proxy running **DMS3Client**, which then applies motion detection processing on the incoming video streams. +The webcam device and the IP camera device--both less smart device clients (LSDCs), incapable of on-board stream processing--must pass raw stream data along to a device proxy running a **DMS3Client** component, which then applies motion detection processing on the incoming video streams. -## 6.0 How DMS3 Works +## How **DMS3** Works ### **DMS3Server** Operation -**DMS3Server** is responsible for signaling the logic of enabling/disabling the video surveillance system to all device client endpoints. That is, **DMS3Server** sends either a `Start` or a `Stop` message to all **DMS3** device clients listening on the network. - -**DMS3Server** does this by periodically scanning the network for the existence of a registered user proxie(s). This device can be anything that exposes its MAC address on the network (e.g., a mobile phone on a home LAN). If that device is found on the network, it's assumed that "someone is home" and so, **DMS3Server** sends out a `Stop` message to all participating device clients, and their respective motion detection application is stopped (if currently running). +The **DMS3Server** component is responsible for signaling the logic of enabling/disabling the video surveillance system to all device client endpoints. That is, the **DMS3Server** sends either a `Start` or a `Stop` message to all **DMS3** device clients configured with a **DMS3Client** component, listening on the network. -If that user proxy MAC "leaves" and is no longer found on the network, it's assumed that "nobody is home", and **DMS3Server** sends out a `Start` message to all participating device clients, and the motion detection application on that client is started (if currently stopped). Similar logic is used in the reverse case: when a user proxy is once again "back home," the motion detection application of each device client is signalled to `Stop`. +**DMS3Server** does this by periodically scanning the network for the existence of registered user proxies. This device can be anything that exposes its MAC address on the network (*e.g.,* a mobile phone on a home LAN). If that device is found on the network, it's assumed that "someone is home" and so **DMS3Server** sends out a `Stop` message to all participating device clients, and their respective motion detection application is subsequently stopped (if currently running). -Alternatively, the *Always On* feature uses time-of-day to enable/disable the surveillance system. **DMS3Server** will look at the time range specified, and if the current time falls between the time range, the motion detection application of all client devices will be started. Once the current time falls outside of the specified time range, the motion detection application for each device client is then stopped. +If that user proxy then "leaves" and is no longer found on the network, it's assumed that "nobody is home", and the **DMS3Server** sends out a `Start` message to all participating device clients, and the motion detection application on that client is started (if currently stopped). Similar logic is used in the reverse case: when a user proxy is once again "back home," the motion detection application of each device client is signalled to `Stop`. -> Note that **DMS3Server** *only signals to participating device clients* the current state of the video surveillance system. Each device client is ultimately responsible for starting/stopping its local instance of the installed motion detection application +Alternatively, the *Always On* feature uses time-of-day to enable/disable the surveillance system. The **DMS3Server** will look at the time range specified, and if the current time falls between the time range, the motion detection application of all client devices will be started. Once the current time falls outside of the specified time range, the motion detection application for each device client is then stopped. ### **DMS3Client** Operation #### Running on Smart Device Clients (SDCs) -**DMS3Client** runs on each configured smart device client endpoint, and is responsible for starting/stopping its locally installed motion detection application. **DMS3Client** does this by periodically listening to **DMS3Server** at the pre-configured [IP address](https://en.wikipedia.org/wiki/IP_address "IP address") and [port](https://en.wikipedia.org/wiki/Computer_port_%28hardware%29 "port") (network [socket address](https://en.wikipedia.org/wiki/Network_socket "socket address")). When **DMS3Client** receives a change in motion detection application state, it either starts or stops its locally-installed motion detection application. +The **DMS3Client** component runs on each configured smart device client endpoint, and is responsible for starting/stopping its locally installed motion detection application. The **DMS3Client** does this by periodically listening to the configured **DMS3Server** at the pre-configured IP address and port (network socket address). When the **DMS3Client** receives a change in motion detection application state, it either starts or stops its locally-installed motion detection application. #### Running with Less Smart Device Clients (LSDCs) @@ -169,55 +211,61 @@ In instances where the device client is "less smart" and unable to process motio ### **DMS3Client** / **DMS3Server** Work Flow -Operationally, **DMS3Server** and all **DMS3Client** device clients work in concert to establish a synchronized video surveillance state across all endpoints: +Operationally, the **DMS3Server** and all **DMS3Client** device clients work together to establish a synchronized video surveillance state across all endpoints: -- **DMS3Server**: usually configured as a daemon running on a central server, and walks a logic tree whenever a client connects (or re-connects) to the server. **DMS3Server** is responsible for answering the question *"should the surveillance system be enabled or disabled right now?"* -- **DMS3Client**: usually configured as a daemon that runs on each of the participating smart device clients, **DMS3Client** regularly polls (at a configurable interval) **DMS3Server**, and receives from **DMS3Server** the current motion detection application state (called *MotionDetectorState*), that is, whether the locally installed motion detection application should be started or stopped +- **DMS3Server**: usually configured as a daemon running on a central server, walks a logic tree whenever a client connects (or re-connects) to the server. **DMS3Server** is responsible for answering the question *"should the surveillance system be enabled or disabled right now?"* +- **DMS3Client**: usually configured as a daemon that runs on each of the participating smart device clients, a **DMS3Client** regularly polls (at a configurable interval) the **DMS3Server**, and receives from the **DMS3Server** the current motion detection application state (called *MotionDetectorState*), that is, whether the locally installed motion detection application should be started or stopped The activity diagram below shows the work flow of these two components: -![dms3_activity_diagram](https://user-images.githubusercontent.com/10182110/28589767-4d57f63a-7134-11e7-9834-1aa51dee38a2.png) +

+ DMS3 Activity Diagram +

### **DMS3Mail** Operation -When using [Motion](https://motion-project.github.io/ "Motion"), **DMS3Mail** is a feature written for **DMS3** that allows for the creation and sending an email whenever a valid capture event is triggered in [Motion](https://motion-project.github.io/ "Motion"). **DMS3Mail** is very tightly integrated into [Motion](https://motion-project.github.io/ "Motion"), where image and video capture events are identified, analyzed, and processed. **DMS3Mail** is triggered by the [`on_picture_save`](https://htmlpreview.github.io/?https://github.com/Motion-Project/motion/blob/master/motion_guide.html#on_picture_save "on_picture_save command") and the [`on_movie_end`](https://htmlpreview.github.io/?https://github.com/Motion-Project/motion/blob/master/motion_guide.html#on_movie_end "on_movie_end command") commands in [Motion](https://motion-project.github.io/ "Motion"). +When using [Motion](https://motion-project.github.io/ "Motion"), **DMS3Mail** is a feature written for **DMS3** that allows for the creation and sending an email whenever a valid capture event is triggered in [Motion](https://motion-project.github.io/ "Motion"). **DMS3Mail** is very tightly integrated into [Motion](https://motion-project.github.io/ "Motion"), where image and video capture events are identified, analyzed, and processed. **DMS3Mail** is triggered by the [`on_picture_save`](https://motion-project.github.io/motion_config.html#on_picture_save) and the [`on_movie_end`](https://motion-project.github.io/motion_config.html#on_movie_end) commands in [Motion](https://motion-project.github.io/ "Motion"). -> **Note:** the optional **DMS3Mail** feature is called by neither **DMS3Client** nor **DMS3Server**. Instead, **DMS3Mail** is called by the [Motion](https://motion-project.github.io/ "Motion") motion detection application via the command-line. +> **Note:** the optional **DMS3Mail** component is called by neither **DMS3Client** nor **DMS3Server**. Instead, **DMS3Mail** is called directly by the [Motion](https://motion-project.github.io/ "Motion") motion detection application. The syntax for these [Motion](https://motion-project.github.io/ "Motion") commands are: -``` - -pixels=%D -filename=%f -camera=%t +```text + -pixels=%D -filename=%f ``` -These commands are managed through the [Motion](https://motion-project.github.io/ "Motion") configuration file called `motion.conf`. +Once configured, **DMS3Mail** will respond to these two [Motion](https://motion-project.github.io/ "Motion") event [hooks](http://en.wikipedia.org/wiki/Hooking "Hooking"), and an email will be generated and sent out with an image file or video clip capturing the surveillance event of interest. -Once configured, **DMS3Mail** will respond to these two [Motion](https://motion-project.github.io/ "Motion") event [hooks](http://en.wikipedia.org/wiki/Hooking "Hooking"), and an email will be generated and sent out with an optional image file or video clip capturing the surveillance event of interest. - -> **Note:** additional information about **DMS3Mail** can be found in the **DMS3** installation file ([`INSTALL.md`](https://github.com/richbl/go-distributed-motion-s3/blob/master/INSTALL.md "INSTALL.md")). - -## 7. DMS3 Requirements +## **DMS3** Requirements +- In order to compile the **DMS3** project components, an operational Go environment is required (this version of **DMS3** was developed using Go 1.17) - A Unix-like operating system installed on the server and smart device client (SDC) endpoints -- While **DMS3** was written and tested under Linux (Ubuntu 17.04), there should be no reason why this won't work under other Linux distributions +- While **DMS3** was written and tested under Linux (Ubuntu 17.04+, and various Debian and Raspian releases), there should be no reason why **DMS3** won't work under other Linux distributions - A motion detection application, such as [Motion](https://motion-project.github.io/ "Motion"), correctly installed and configured with appropriate video devices configured on all smart device clients -- Specific Unix-like commands and tools used by **DMS3** components include (all should already exist on most Unix-like operating systems): - - [arp](http://en.wikipedia.org/wiki/Address_Resolution_Protocol "arp"): address resolution protocol +- Specific Unix and Unix-like commands and tools used by **DMS3** components include: + - [aplay](http://en.wikipedia.org/wiki/Aplay "aplay"): ALSA audio player (optional) + - [bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell) "bash"): a Unix shell and command language + - [cat](https://en.wikipedia.org/wiki/Cat_(Unix) "cat"): a standard Unix utility that reads files/input and writes them to standard output + - [env](https://en.wikipedia.org/wiki/Env "env"): Unix shell command to run a program in an altered environment - [grep](http://en.wikipedia.org/wiki/Grep "grep"): globally search a regular expression and print + - [ip](https://linux.die.net/man/8/ip "ip"): displays or manipulate routing, devices, policy routing, and tunnels - [pgrep](http://en.wikipedia.org/wiki/Pgrep "pgrep"): globally search a regular expression and print - [ping](http://en.wikipedia.org/wiki/Ping_(networking_utility) "ping"): ICMP network packet echo/response tool - - [aplay](http://en.wikipedia.org/wiki/Aplay "aplay"): ALSA audio player (optional) + - [pkill](https://en.wikipedia.org/wiki/Pkill "pkill"): globally search a regular expression and send signals to a process + +### Wifi MAC Randomization Techniques + +At its core, **DMS3** sensing relies on the concept of a *user proxy*. In this context, *a user proxy is any device representing a user that can be sensed on a home network*. A smartphone is an excellent user proxy, assuming that a user's smartphone is active on the home network when the user is "at home," and drops from the network when the user leaves and is then "not at home." **DMS3** performs this sensing by searching the end user's network for MAC addresses registered during the configuration of the **DMS3Server** component (in the `dms3server.toml` file). -## 8. DMS3 Installation +Historically, MAC addresses have always represented, 1-for-1, the underlying hardware. However, more recently, and as a broader privacy policy, some device vendors now provide users the option to have their device generate MAC addresses randomly for over-the-air communications. This feature can disrupt the sensing services used by the **DMS3Server** component. -**DMS3** provides two separate installation documents: +As a result, it's important to review your smartphone (or other user proxies) privacy policies and feature options and configure it accordingly. -- [Quick Installation](https://github.com/richbl/go-distributed-motion-s3/blob/master/QUICK_INSTALL.md): uses the available `dms3build` build tools and installer to provided automated installation of **DMS3** components across participating hardware devices -- [Manual Installation](https://github.com/richbl/go-distributed-motion-s3/blob/master/MANUAL_INSTALL.md): uses project sources to first compile for specific hardware device platforms, and then manually install **DMS3** components +## **DMS3** Installation -> **Note:** to learn more about the technical details of the **DMS3** project, refer to the [Manual Installation](https://github.com/richbl/go-distributed-motion-s3/blob/master/MANUAL_INSTALL.md) documentation, as this document provides much greater technical depth in describing the **DMS3** installation process +A separate installation document is [available here](https://github.com/richbl/go-distributed-motion-s3/blob/master/MANUAL_INSTALL.md). -## 9. License +## License The MIT License (MIT) diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..c98c8bf --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,5 @@ +# Roadmap for Distributed Motion Surveillance Security System (DMS3) + +Check out our ever-changing roadmap on the [DMS3 Roadmap Project site](https://github.com/richbl/go-distributed-motion-s3/projects/1). + +We're always looking for your input and ideas, so please let us know how you'd like to improve **DMS3**. diff --git a/compile_dms3.go b/cmd/compile_dms3/compile_dms3.go similarity index 63% rename from compile_dms3.go rename to cmd/compile_dms3/compile_dms3.go index e7545a7..2d06168 100644 --- a/compile_dms3.go +++ b/cmd/compile_dms3/compile_dms3.go @@ -8,13 +8,20 @@ package main import ( - "go-distributed-motion-s3/dms3build" + "path/filepath" + + "github.com/richbl/go-distributed-motion-s3/dms3build" + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) func main() { - // build platform-specific components into release folder + dms3libs.LoadLibConfig(filepath.Join("config", "dms3libs.toml")) + + // create release folder dms3build.BuildReleaseFolder() + + // build platform-specific components into release folder dms3build.BuildComponents() // copy service daemons into release folder @@ -23,8 +30,11 @@ func main() { // copy dms3server media files into release folder dms3build.CopyMediaFiles() - // copy dms3dashboard html files into release folder - dms3build.CopyDashboardFiles() + // copy dms3dashboard html file and assets into release folder + dms3build.CopyComponents("dms3dashboard") + + // copy dms3mail html file and assets into release folder + dms3build.CopyComponents("dms3mail") // copy TOML files into release folder dms3build.CopyConfigFiles() diff --git a/cmd/dms3client/dms3client.go b/cmd/dms3client/dms3client.go new file mode 100644 index 0000000..c52c9cb --- /dev/null +++ b/cmd/dms3client/dms3client.go @@ -0,0 +1,9 @@ +// Package main dms3client initializes a dms3client device component +// +package main + +import "github.com/richbl/go-distributed-motion-s3/dms3client" + +func main() { + dms3client.Init("/etc/distributed-motion-s3") +} diff --git a/cmd/dms3client_remote_installer/dms3client_remote_installer.go b/cmd/dms3client_remote_installer/dms3client_remote_installer.go new file mode 100644 index 0000000..9a877bb --- /dev/null +++ b/cmd/dms3client_remote_installer/dms3client_remote_installer.go @@ -0,0 +1,41 @@ +// this script will be copied to the dms3 device component platform, executed, +// and then deleted automatically +// +// NOTE: must be run with admin privileges on the remote device +// +package main + +import ( + "path/filepath" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" +) + +func main() { + + // NOTE: this component is run on the remote client device (per dms3build.toml configuration) + + // load libs config file from dms3_release folder on remote device + dms3libs.LoadLibConfig(filepath.Join("dms3_release", "config", "dms3libs", "dms3libs.toml")) + + binaryInstallDir := filepath.Join(string(filepath.Separator), "usr", "local", "bin") + configInstallDir := filepath.Join(string(filepath.Separator), "etc", "distributed-motion-s3") + logDir := filepath.Join(string(filepath.Separator), "var", "log", "dms3") + + // move binary files into binaryInstallDir + dms3libs.CopyFile(filepath.Join("dms3_release", "cmd", "dms3client"), filepath.Join(binaryInstallDir, "dms3client")) + dms3libs.CopyFile(filepath.Join("dms3_release", "cmd", "dms3mail"), filepath.Join(binaryInstallDir, "dms3mail")) + + // create log folder + dms3libs.MkDir(logDir) + + // copy configuration files into configInstallDir + dms3libs.MkDir(configInstallDir) + dms3libs.CopyDir(filepath.Join("dms3_release", "config", "dms3client"), configInstallDir) + dms3libs.CopyDir(filepath.Join("dms3_release", "config", "dms3dashboard"), configInstallDir) + dms3libs.CopyDir(filepath.Join("dms3_release", "config", "dms3libs"), configInstallDir) + dms3libs.CopyDir(filepath.Join("dms3_release", "config", "dms3mail"), configInstallDir) + + dms3libs.RmDir("dms3_release") + +} diff --git a/cmd/dms3mail/dms3mail.go b/cmd/dms3mail/dms3mail.go new file mode 100644 index 0000000..6a352fe --- /dev/null +++ b/cmd/dms3mail/dms3mail.go @@ -0,0 +1,9 @@ +// Package main dms3mail initializes a dms3mail device component +// +package main + +import "github.com/richbl/go-distributed-motion-s3/dms3mail" + +func main() { + dms3mail.Init("/etc/distributed-motion-s3") +} diff --git a/cmd/dms3server/dms3server.go b/cmd/dms3server/dms3server.go new file mode 100644 index 0000000..522b7fe --- /dev/null +++ b/cmd/dms3server/dms3server.go @@ -0,0 +1,9 @@ +// Package main dms3server initializes a dms3server device component +// +package main + +import "github.com/richbl/go-distributed-motion-s3/dms3server" + +func main() { + dms3server.Init("/etc/distributed-motion-s3") +} diff --git a/cmd/dms3server_remote_installer/dms3server_remote_installer.go b/cmd/dms3server_remote_installer/dms3server_remote_installer.go new file mode 100644 index 0000000..4733028 --- /dev/null +++ b/cmd/dms3server_remote_installer/dms3server_remote_installer.go @@ -0,0 +1,39 @@ +// this script will be copied to the dms3 device component platform, executed, +// and then deleted automatically +// +// NOTE: must be run with admin privileges on the remote device +// +package main + +import ( + "path/filepath" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" +) + +func main() { + + // NOTE: this component is run on the remote server device (per dms3build.toml configuration) + + // load libs config file from dms3_release folder on remote device + dms3libs.LoadLibConfig(filepath.Join("dms3_release", "config", "dms3libs", "dms3libs.toml")) + + binaryInstallDir := filepath.Join(string(filepath.Separator), "usr", "local", "bin") + configInstallDir := filepath.Join(string(filepath.Separator), "etc", "distributed-motion-s3") + logDir := filepath.Join(string(filepath.Separator), "var", "log", "dms3") + + // move binary files into binaryInstallDir + dms3libs.CopyFile(filepath.Join("dms3_release", "cmd", "dms3server"), filepath.Join(binaryInstallDir, "dms3server")) + + // create log folder + dms3libs.MkDir(logDir) + + // copy configuration files into configInstallDir + dms3libs.MkDir(configInstallDir) + dms3libs.CopyDir(filepath.Join("dms3_release", "config", "dms3server"), configInstallDir) + dms3libs.CopyDir(filepath.Join("dms3_release", "config", "dms3dashboard"), configInstallDir) + dms3libs.CopyDir(filepath.Join("dms3_release", "config", "dms3libs"), configInstallDir) + + dms3libs.RmDir("dms3_release") + +} diff --git a/install_dms3.go b/cmd/install_dms3/install_dms3.go similarity index 56% rename from install_dms3.go rename to cmd/install_dms3/install_dms3.go index a870a00..58b4cee 100644 --- a/install_dms3.go +++ b/cmd/install_dms3/install_dms3.go @@ -8,23 +8,24 @@ package main import ( - "go-distributed-motion-s3/dms3build" - "go-distributed-motion-s3/dms3libs" + "path/filepath" + + "github.com/richbl/go-distributed-motion-s3/dms3build" + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) func main() { - // sets release path to dms3_release folder - releasePath := dms3build.ReleasePath() + releasePath := "dms3_release" // confirm existence of dms3_release folder based on releasePath - dms3build.ConfirmReleaseFolder(releasePath["releaseFolder"]) + dms3build.ConfirmReleaseFolder(releasePath) // read configuration in from dms3build TOML - dms3libs.LoadComponentConfig(&dms3build.BuildConfig, releasePath["configFolder"]+"/dms3build.toml") + dms3libs.LoadComponentConfig(&dms3build.BuildConfig, filepath.Join(releasePath, "config", "dms3build", "dms3build.toml")) // install components onto device platforms - dms3build.InstallClientComponents(releasePath["releaseFolder"]) - dms3build.InstallServerComponents(releasePath["releaseFolder"]) + dms3build.InstallClientComponents(releasePath) + dms3build.InstallServerComponents(releasePath) } diff --git a/config/dms3build.toml b/config/dms3build.toml index 605823f..7102c04 100644 --- a/config/dms3build.toml +++ b/config/dms3build.toml @@ -1,5 +1,5 @@ -# Distributed-Motion-S3 (DMS3) CLIENT configuration file -# TOML 0.4.0 +# Distributed-Motion-S3 DMS3Build Component Configuration File +# 1.4.0 [Clients] diff --git a/config/dms3client.toml b/config/dms3client.toml index 52c176b..85afae7 100644 --- a/config/dms3client.toml +++ b/config/dms3client.toml @@ -1,43 +1,45 @@ -# Distributed-Motion-S3 (DMS3) CLIENT configuration file -# TOML 0.4.0 +# Distributed-Motion-S3 DMS3Client Component Configuration File +# 1.4.0 [Server] - # IP is the address on which the dms server is running - IP = "10.10.10.9" + # IP is the address on which the DMS3Server is running + # + IP = "10.10.10.9" - # Port is the port on which the dms server is running - Port = 1965 + # Port is the port on which the DMS3Server is running + # + Port = 49300 - # CheckInterval is the interval (in seconds) for checking the dms server - CheckInterval = 15 + # CheckInterval is the interval (in seconds) for checking with the DMS3Server + # + CheckInterval = 15 [Logging] - # LogLevel sets the log levels for application logging using the following table: - # - # 0 - OFF, no logging - # 1 - FATAL, report fatal events - # 2 - INFO, report informational events - # 4 - DEBUG, report debugging events - # - LogLevel = 2 - - # LogDevice determines to what device logging should be set using the following table: - # - # 0 - STDOUT (terminal) - # 1 - log file - # - # Ignored if LogLevel == 0 - # - LogDevice = 0 - - # LogFilename is the logging filename - # - # Ignored if LogLevel == 0 or LogDevice == 0 - # - LogFilename = "dms3client.log" - - # LogLocation is the location of logfile (absolute path; must have r/w permissions) - # - # Ignored if LogLevel == 0 or LogDevice == 0 - # - LogLocation = "/var/log/dms3" + + # LogLevel sets the log levels for application logging using the following table: + # Note that DEBUG > INFO > FATAL, so DEBUG includes all log levels + # + # 0 - OFF, no logging + # 1 - FATAL, report fatal events + # 2 - INFO, report informational events + # 4 - DEBUG, report debugging events + # + LogLevel = 2 + + # LogDevice determines to what output logging should be set using the following table: + # Ignored if LogLevel == 0 + # + # 0 - STDOUT (terminal) + # 1 - log file + # + LogDevice = 0 + + # LogFilename is the logging filename + # Ignored if LogLevel == 0 or LogDevice == 0 + # + LogFilename = "dms3client.log" + + # LogLocation is the location of logfile (absolute path; must have r/w permissions) + # Ignored if LogLevel == 0 or LogDevice == 0 + # + LogLocation = "/var/log/dms3" diff --git a/config/dms3dashboard.toml b/config/dms3dashboard.toml index c9feb2a..30c45fd 100644 --- a/config/dms3dashboard.toml +++ b/config/dms3dashboard.toml @@ -1,32 +1,70 @@ +# Distributed-Motion-S3 DMS3Dashboard Component Configuration File +# 1.4.0 + [Server] - # Enables (true) or disables (false) the DMS3 dashboard running over HTTP on the server - Enable = true - # Port is the port on which to run the dashboard server - Port = 8081 + # Configuration elements for DMS3Server + # Ignored if Server.EnableDashboard == false (set in dms3server.toml) + + # Port is the port on which to run the DMS3Dashboard server + # + Port = 8081 + + # Filename of DMS3Dashboard HTML dashboard template file + # + Filename = "dms3dashboard.html" - # Filename of HTML dashboard template file - Filename = "dashboard.html" + # FileLocation is where the HTML dashboard template file is located + # + # By default, the value is "" (empty string), which sets to the path of the release dashboard + # folder (e.g., /etc/distributed-motion-s3/dms3dashboard/dms3dashboard.html) + # Any other filepath/filename will be used if valid + # + FileLocation = "" - # FileLocation is where the HTML dashboard template file is located - # By default, the value is "" (empty string), which sets to the path of the release dashboard - # folder (e.g., /etc/distributed-motion-s3/dms3dashboard/dashboard.html) - # Any other filepath/filename will be used if valid, else set to local development folder - # - # Ignored if Dashboard.Enable == false - # - FileLocation = "" + # Dashboard title + # + Title = "DMS3 Dashboard" - # Dashboard title - Title = "DMS3 Dashboard" + # Enables (true) or disables (false) to alphabetically re-sort devices displayed in the + # dashboard template + # + ReSort = true + + # Enables (true) or disables (false) to make DMS3Server the first of all devices displayed + # in the dashboard template + # Ignored if ReSort == false + # + ServerFirst = true + + # Device status identifies the stages when a device is no longer reporting status updates + # to the dashboard server + # + # Device status values are defined as a multiplier of Server.CheckInterval + # (default = 15 seconds) declared/defined in the dms3server.toml file + # + # If the device check interval for the dashboard server is every 15 seconds (default), and + # the device status multiplier for caution (DeviceStatus.Caution) is 200 (default), then the + # dashboard server will report a device caution status (yellow device icon) after 3000 + # seconds (50 minutes) of no status updates received from that device + # + # Device status will continue to progress through each of the stages identified below, or reset + # to a normal device status if device again reports in to the dashboard server + # + [Server.DeviceStatus] + Caution = 200 # yellow device icon on the dashboard after 50 minutes (200*15 seconds) + Danger = 3000 # red device icon on the dashboard after 12.5 hours (3000*15 seconds) + Missing = 28800 # removed from dashboard server after 5 days (28800*15 seconds) [Client] - # ImagesFolder is the location of where the motion detection application stores its - # motion-triggered image/movie files on the client (e.g., should match the target_dir parameter - # used in the Motion application) - # Used in determining the client "events" metrics, presented through the dashboard - # - # If the value is "" (empty string), this value is disabled (not reported) on the dashboard - # Ignored if [Server] Enable == false - # - ImagesFolder = "/home/pi/motion_pics" + + # Configuration elements for DMS3Client + # + # ImagesFolder is the location of where the motion detection application stores its + # motion-triggered image/movie files on the client (e.g., matching the target_dir parameter + # used in the Motion application) + # + # Used in determining the client "events" metric, presented through the dashboard + # If the value is "" (empty string), this metric is disabled (not reported) on the dashboard + # + ImagesFolder = "/home/richbl/motion_pics" diff --git a/config/dms3libs.toml b/config/dms3libs.toml index ea75737..9d9a1a4 100644 --- a/config/dms3libs.toml +++ b/config/dms3libs.toml @@ -1,11 +1,17 @@ -# Distributed-Motion-S3 (DMS3) LIBS configuration file -# TOML 0.4.0 +# Distributed-Motion-S3 DMS3Libs Component Configuration File +# 1.4.0 -# SysCommands provide a location mapping of required system commands [SysCommands] - APLAY = "/usr/bin/aplay" - ARP = "/usr/sbin/arp" - CAT = "/bin/cat" - GREP = "/bin/grep" - PGREP = "/usr/bin/pgrep" - PING = "/bin/ping" \ No newline at end of file + + # SysCommands provide a location mapping of required system commands used by various DMS3 + # components + # + APLAY = "/usr/bin/aplay" + BASH = "/usr/bin/bash" + CAT = "/usr/bin/cat" + ENV = "/usr/bin/env" + GREP = "/usr/bin/grep" + IP = "/usr/sbin/ip" + PGREP = "/usr/bin/pgrep" + PING = "/usr/bin/ping" + PKILL = "/usr/bin/pkill" \ No newline at end of file diff --git a/config/dms3mail.toml b/config/dms3mail.toml index 09a0168..4af6941 100644 --- a/config/dms3mail.toml +++ b/config/dms3mail.toml @@ -1,67 +1,72 @@ -# Distributed-Motion-S3 (DMS3) MAIL configuration file -# TOML 0.4.0 +# Distributed-Motion-S3 DMS3Mail Component Configuration File +# 1.4.0 -[Email] - # EmailFrom is the email sender - From = "dms3mail@businesslearninginc.com" +# Filename of HTML email template file +# +Filename = "dms3mail.html" - # EmailTo is the email recipient - To = "user@gmail.com" +# FileLocation is where the HTML email template file is located +# By default, the value is "" (empty string), which sets the path to the release email +# folder (e.g., /etc/distributed-motion-s3/dms3mail) +# +# Any other filepath/filename will be used, if valid +# +FileLocation = "" - # EmailBody is the email body - # Note that reserved words use the syntax !ALLCAPS and are parsed and replaced - # - Body = "Motion detected an event of importance. The event (!EVENT) shows !PIXELS pixels changed, and was captured by Camera !CAMERA." +[Email] -[SMTP] - # SMTPAddress is the SMTP address of the recipient - Address = "smtp.gmail.com" + # EmailFrom is the email sender + # + From = "dms3mail@businesslearninginc.com" - # SMTPPort is the port used by the recipient email account - Port = 587 + # EmailTo is the email recipient + # + To = "user@gmail.com" - # SMTPDomain is the receiving email domain - Domain = "localhost" +[SMTP] - # SMTPUsername is the username of the recipient - Username = "user" + # SMTPAddress is the host of the SMTP server + # + Address = "smtp.gmail.com" - # SMTPPassword is the password of the recipient - Password = "password" + # SMTPPort is the port of the SMTP server + # + Port = 587 - # SMTPAuthentication is the email server authentication scheme - Authentication = "plain" + # SMTPUsername is the username to use to authenticate to the SMTP server + # + Username = "user" - # SMTPEnableStartTLSAuto indicates whether TLS is used - EnableStartTLSAuto = true + # SMTPPassword is the password to use to authenticate to the SMTP server + # + Password = "password" [Logging] - # LogLevel sets the log levels for application logging using the following table: - # - # 0 - OFF, no logging - # 1 - FATAL, report fatal events - # 2 - INFO, report informational events - # 4 - DEBUG, report debugging events - # - LogLevel = 1 - # LogDevice determines to what device logging should be set using the following table: - # - # 0 - STDOUT (terminal) - # 1 - log file - # - # Ignored if LogLevel == 0 - # - LogDevice = 0 + # LogLevel sets the log levels for application logging using the following table: + # Note that DEBUG > INFO > FATAL, so DEBUG includes all log levels + # + # 0 - OFF, no logging + # 1 - FATAL, report fatal events + # 2 - INFO, report informational events + # 4 - DEBUG, report debugging events + # + LogLevel = 1 + + # LogDevice determines to what device logging should be set using the following table: + # Ignored if LogLevel == 0 + # + # 0 - STDOUT (terminal) + # 1 - log file + # + LogDevice = 0 - # LogFilename is the logging filename - # - # Ignored if LogLevel == 0 or LogDevice == 0 - # - LogFilename = "dms3mail.log" + # LogFilename is the logging filename + # Ignored if LogLevel == 0 or LogDevice == 0 + # + LogFilename = "dms3mail.log" - # LogLocation is the location of logfile (absolute path; must have r/w permissions) - # - # Ignored if LogLevel == 0 or LogDevice == 0 - # - LogLocation = "/var/log/dms3" \ No newline at end of file + # LogLocation is the location of logfile (absolute path; must have r/w permissions) + # Ignored if LogLevel == 0 or LogDevice == 0 + # + LogLocation = "/var/log/dms3" diff --git a/config/dms3server.toml b/config/dms3server.toml index cffbebe..a587194 100644 --- a/config/dms3server.toml +++ b/config/dms3server.toml @@ -1,88 +1,103 @@ -# Distributed-Motion-S3 (DMS3) SERVER configuration file -# TOML 0.4.0 +# Distributed-Motion-S3 DMS3Server Component Configuration File +# 1.4.0 [Server] - # Port is the port on which to run the motion server - Port = 1965 - # CheckInterval is the interval (in seconds) between local checks for change to motion_state - CheckInterval = 15 + # Port is the port on which to run DMS3Server + # + Port = 49300 + + # CheckInterval is the interval (in seconds) between local checks for change to motion_state + # + CheckInterval = 15 + + # Enables (true) or disables (false) the DMS3Dashboard running over HTTP on this server + # + EnableDashboard = true [Audio] - # Enables (true) or disables (false) the play-back of audio on motion detector application - # start/stop - # - Enable = true - - # PlayMotionStart is the audio file played when the motion detector application is activated - # By default, the value is "" (empty string), which gets set to the path of the release /media - # folder and filename (e.g., /etc/distributed-motion-s3/dms3server/media/motion_start.wav) - # Any other filepath/filename will be used if valid, else set to local development folder - # - # Ignored if Audio.Enable == false - # - PlayMotionStart = "" - - # PlayMotionStart is the audio file played when the motion detector application is activated - # By default, the value is "" (empty string), which gets set to the path of the release /media - # folder and filename (e.g., /etc/distributed-motion-s3/dms3server/media/motion_stop.wav) - # Any other filepath/filename will be used if valid, else set to local development folder - # - # Ignored if Audio.Enable == false - # - PlayMotionStop = "" + + # Enables (true) or disables (false) the play-back of audio on motion detector application + # start/stop + # + Enable = true + + # PlayMotionStart is the audio file played when the motion detector application is activated + # By default, the value is "" (empty string), which gets set to the path of the release /media + # folder and filename (e.g., /etc/distributed-motion-s3/dms3server/media/motion_start.wav) + # + # Any other filepath/filename will be used if valid, else set to local development folder + # Ignored if Audio.Enable == false + # + PlayMotionStart = "" + + # PlayMotionStart is the audio file played when the motion detector application is activated + # By default, the value is "" (empty string), which gets set to the path of the release /media + # folder and filename (e.g., /etc/distributed-motion-s3/dms3server/media/motion_stop.wav) + # + # Any other filepath/filename will be used if valid, else set to local development folder + # Ignored if Audio.Enable == false + # + PlayMotionStop = "" [AlwaysOn] - # Enables (true) or disables (false) motion detector application based on time-of-day - Enable = true - # TimeRange is the start and end times (24-hour format) for motion to always be enabled, - # regardless of absence/existence of user proxy device(s) on the LAN - # - # Ignored if AlwaysOn.Enable == false - # - TimeRange = ["2300", "0400"] + # Enables (true) or disables (false) motion detector application based on time-of-day + # + Enable = true + + # TimeRange is the start and end times (24-hour format) for motion sensing to always be enabled, + # regardless of absence/existence of user proxy device(s) on the LAN + # Ignored if AlwaysOn.Enable == false + # + TimeRange = ["2300", "0400"] [UserProxy] - # IPBase is the first three address octets defining the LAN (e.g., 10.10.10.) where user - # proxies (devices representing users on the network, such as a smartphone) will - # be scanned for to determine when motion should be run - # - IPBase = "10.10.10." - # IPRange is the fourth address octet defined as a range (e.g., 100..254) - IPRange = [100, 254] + # IPBase is the first three address octets defining the LAN (e.g., 10.10.10.) where user + # proxies (devices representing users on the network, such as a smartphone) will + # be scanned for to determine when motion should be run + # + IPBase = "10.10.10." - # MacsToFind are the MAC addresses of user proxy device(s) to search for on the LAN - MacsToFind = ["24:da:9b:0d:53:8f", "f8:cf:c5:d2:bb:9e"] + # IPRange is the fourth address octet defined as a range (e.g., 100..254) in which to search for + # user proxies + # + IPRange = [100, 254] + + # MacsToFind are the MAC addresses (e.g., "24:da:9b:0d:53:8f") of user proxy device(s) + # to search for on the LAN + # + # IMPORTANT: use colon delimiters only, as this is what the 'ip' command expects + # + MacsToFind = ["24:da:9b:0d:53:8f", "f8:cf:c5:d2:bb:9e"] [Logging] - # LogLevel sets the log levels for application logging using the following table: - # - # 0 - OFF, no logging - # 1 - FATAL, report fatal events - # 2 - INFO, report informational events - # 4 - DEBUG, report debugging events - # - LogLevel = 2 - - # LogDevice determines to what device logging should be set using the following table: - # - # 0 - STDOUT (terminal) - # 1 - log file - # - # Ignored if LogLevel == 0 - # - LogDevice = 0 - - # LogFilename is the logging filename - # - # Ignored if LogLevel == 0 or LogDevice == 0 - # - LogFilename = "dms3server.log" - - # LogLocation is the location of logfile (absolute path; must have r/w permissions) - # - # Ignored if LogLevel == 0 or LogDevice == 0 - # - LogLocation = "/var/log/dms3" + + # LogLevel sets the log levels for application logging using the following table + # Note that DEBUG > INFO > FATAL, so DEBUG includes all log levels + # + # 0 - OFF, no logging + # 1 - FATAL, report fatal events + # 2 - INFO, report informational events + # 4 - DEBUG, report debugging events + # + LogLevel = 2 + + # LogDevice determines to what device logging should be set using the following table: + # Ignored if LogLevel == 0 + # + # 0 - STDOUT (terminal) + # 1 - log file + # + LogDevice = 0 + + # LogFilename is the logging filename + # Ignored if LogLevel == 0 or LogDevice == 0 + # + LogFilename = "dms3server.log" + + # LogLocation is the location of logfile (absolute path; must have r/w permissions) + # Ignored if LogLevel == 0 or LogDevice == 0 + # + LogLocation = "/var/log/dms3" diff --git a/dms3build/compiler_config.go b/dms3build/compiler_config.go index fc1abaa..5f6f700 100644 --- a/dms3build/compiler_config.go +++ b/dms3build/compiler_config.go @@ -6,49 +6,49 @@ package dms3build type structComponent struct { srcName string // component source filename exeName string // compiled filename - dirName string // location for compiled component in release folder + dirName string // location for components in release folder configFilename string // relevant config (TOML) file compile bool // whether component should be compiled } var components = []structComponent{ { - srcName: "go_dms3client.go", - exeName: "go_dms3client", + srcName: "cmd/dms3client/dms3client.go", + exeName: "dms3client", dirName: "dms3client", configFilename: "dms3client.toml", compile: true, }, { - srcName: "go_dms3server.go", - exeName: "go_dms3server", + srcName: "cmd/dms3server/dms3server.go", + exeName: "dms3server", dirName: "dms3server", configFilename: "dms3server.toml", compile: true, }, { - srcName: "go_dms3mail.go", - exeName: "go_dms3mail", + srcName: "cmd/dms3mail/dms3mail.go", + exeName: "dms3mail", dirName: "dms3mail", configFilename: "dms3mail.toml", compile: true, }, { - srcName: "install_dms3.go", + srcName: "cmd/install_dms3/install_dms3.go", exeName: "install_dms3", dirName: "dms3build", configFilename: "dms3build.toml", compile: true, }, { - srcName: "dms3build/remote_installers/dms3client_remote_installer.go", + srcName: "cmd/dms3client_remote_installer/dms3client_remote_installer.go", exeName: "dms3client_remote_installer", dirName: "dms3build", configFilename: "", compile: true, }, { - srcName: "dms3build/remote_installers/dms3server_remote_installer.go", + srcName: "cmd/dms3server_remote_installer/dms3server_remote_installer.go", exeName: "dms3server_remote_installer", dirName: "dms3build", configFilename: "", @@ -77,6 +77,7 @@ type platformType string const ( linuxArm6 platformType = "linuxArm6" linuxArm7 platformType = "linuxArm7" + linuxArm8 platformType = "linuxArm8" linuxAMD64 platformType = "linuxAMD64" ) @@ -96,6 +97,10 @@ var BuildEnv = map[platformType]structPlatform{ dirName: "linux_arm7", compileTags: "GOOS=linux GOARCH=arm GOARM=7", }, + linuxArm8: { + dirName: "linux_arm8", + compileTags: "GOOS=linux GOARCH=arm64", + }, linuxAMD64: { dirName: "linux_amd64", compileTags: "GOOS=linux GOARCH=amd64", diff --git a/dms3build/lib_build.go b/dms3build/lib_build.go index 3c989b0..5bb2aab 100644 --- a/dms3build/lib_build.go +++ b/dms3build/lib_build.go @@ -5,135 +5,121 @@ package dms3build import ( "errors" "fmt" - "go-distributed-motion-s3/dms3libs" "os" "path/filepath" "strconv" "github.com/mrgleam/easyssh" + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) // BuildReleaseFolder creates the directory structure for each platform passed into it +// func BuildReleaseFolder() { dms3libs.RmDir("dms3_release") for platformType := range BuildEnv { - fmt.Print("Creating release folder for " + BuildEnv[platformType].dirName + " platform... ") - dms3libs.MkDir(filepath.Join("dms3_release", BuildEnv[platformType].dirName)) - fmt.Println("Success") + fmt.Println("Creating release folder for " + BuildEnv[platformType].dirName + " platform...") + dms3libs.MkDir(filepath.Join("dms3_release", "cmd", BuildEnv[platformType].dirName)) } for itr := range components { - fmt.Print("Creating release folder for " + components[itr].exeName + " component... ") + fmt.Println("Creating release folder for " + components[itr].exeName + " component...") dirName := components[itr].dirName if components[itr].dirName == "dms3server" { dirName = filepath.Join(dirName, "media") } - dms3libs.MkDir(filepath.Join("dms3_release", dirName)) - fmt.Println("Success") + dms3libs.MkDir(filepath.Join("dms3_release", "config", dirName)) } - fmt.Println() - } // BuildComponents compiles dms3 components for each platform passed into it +// func BuildComponents() { for platformType := range BuildEnv { - fmt.Print("Building dms3 components for " + BuildEnv[platformType].dirName + " platform... ") + fmt.Println("Building dms3 components for " + BuildEnv[platformType].dirName + " platform...") for jtr := range components { if components[jtr].compile { - _, err := dms3libs.RunCommand("env " + BuildEnv[platformType].compileTags + " go build -o " + filepath.Join("dms3_release", BuildEnv[platformType].dirName) + "/" + components[jtr].exeName + " " + components[jtr].srcName) - dms3libs.CheckErr(err) + + // dms3 installer (install_dms3) is only built once (natively on linuxAMD64) into dms3_release root + if components[jtr].exeName == "install_dms3" { + + if platformType == linuxAMD64 { + _, err := dms3libs.RunCommand(dms3libs.LibConfig.SysCommands["ENV"] + " " + BuildEnv[platformType].compileTags + " go build -o " + filepath.Join("dms3_release", "cmd", components[jtr].exeName) + " " + components[jtr].srcName) + dms3libs.CheckErr(err) + } + + } else { + _, err := dms3libs.RunCommand(dms3libs.LibConfig.SysCommands["ENV"] + " " + BuildEnv[platformType].compileTags + " go build -o " + filepath.Join("dms3_release", "cmd", BuildEnv[platformType].dirName, components[jtr].exeName) + " " + components[jtr].srcName) + dms3libs.CheckErr(err) + } + } } - fmt.Println("Success") } - fmt.Println() } // CopyServiceDaemons copies daemons into release folder +// func CopyServiceDaemons() { - fmt.Print("Copying dms3 service daemons into dms3_release folder... ") - dms3libs.CopyFile("dms3client/daemons/systemd/dms3client.service", filepath.Join("dms3_release", "dms3client/dms3client.service")) - dms3libs.CopyFile("dms3server/daemons/systemd/dms3server.service", filepath.Join("dms3_release", "dms3server/dms3server.service")) - fmt.Println("Success") - fmt.Println() + fmt.Println("Copying dms3 service daemons into dms3_release folder...") + + dms3libs.CopyFile(filepath.Join("dms3client", "daemons", "systemd", "dms3client.service"), filepath.Join("dms3_release", "config", "dms3client", "dms3client.service")) + dms3libs.CopyFile(filepath.Join("dms3server", "daemons", "systemd", "dms3server.service"), filepath.Join("dms3_release", "config", "dms3server", "dms3server.service")) + } // CopyMediaFiles copies dms3server media files into release folder +// func CopyMediaFiles() { - fmt.Print("Copying dms3server media files (WAV) into dms3_release folder... ") - dms3libs.CopyFile("dms3server/media/motion_start.wav", filepath.Join("dms3_release", "dms3server/media/motion_start.wav")) - dms3libs.CopyFile("dms3server/media/motion_stop.wav", filepath.Join("dms3_release", "dms3server/media/motion_stop.wav")) - fmt.Println("Success") - fmt.Println() + fmt.Println("Copying dms3server media files (WAV) into dms3_release folder...") + + dms3libs.CopyFile(filepath.Join("dms3server", "media", "motion_start.wav"), filepath.Join("dms3_release", "config", "dms3server", "media", "motion_start.wav")) + dms3libs.CopyFile(filepath.Join("dms3server", "media", "motion_stop.wav"), filepath.Join("dms3_release", "config", "dms3server", "media", "motion_stop.wav")) } -// CopyDashboardFiles copies the dms3dashboard html file into release folder -func CopyDashboardFiles() { +// CopyComponents copies component html files and assets into the release folder +func CopyComponents(component string) { + + fmt.Println("Copying " + component + " file (HTML) into dms3_release folder...") + dms3libs.CopyFile(filepath.Join(component, component+".html"), filepath.Join("dms3_release", "config", component, component+".html")) - fmt.Print("Copying dms3dashboard file (HTML) into dms3_release folder... ") - dms3libs.CopyFile("dms3dashboard/dashboard.html", filepath.Join("dms3_release", "dms3dashboard/dashboard.html")) - fmt.Print("Copying dms3dashboard assets into dms3_release folder... ") - dms3libs.CopyDir("dms3dashboard/assets", filepath.Join("dms3_release", "dms3dashboard")) - fmt.Println("Success") - fmt.Println() + fmt.Println("Copying " + component + " assets into dms3_release folder...") + dms3libs.CopyDir(filepath.Join(component, "assets"), filepath.Join("dms3_release", "config", component)) } -// CopyConfigFiles copies dms3server media files into release folder +// CopyConfigFiles copies config files into release folder +// func CopyConfigFiles() { - fmt.Print("Copying dms3 component config files (TOML) into dms3_release folder... ") + fmt.Println("Copying dms3 component config files (TOML) into dms3_release folder...") for itr := range components { if components[itr].configFilename != "" { - dms3libs.CopyFile(filepath.Join("config", components[itr].configFilename), filepath.Join("dms3_release", components[itr].dirName+"/"+components[itr].configFilename)) + dms3libs.CopyFile(filepath.Join("config", components[itr].configFilename), filepath.Join("dms3_release", "config", components[itr].dirName, components[itr].configFilename)) } } - fmt.Println("Success") - fmt.Println() - -} - -// ReleasePath sets the installer release path based on whether called from source project or -// from a binary release folder -// -func ReleasePath() map[string]string { - - paths := make(map[string]string) - - if isRunningRelease() { - base := filepath.Dir(filepath.Dir(execFilePath())) - paths["configFolder"] = filepath.Join(base, "dms3_release/dms3build") - paths["releaseFolder"] = filepath.Join(base, "dms3_release") - } else { - base, _ := os.Getwd() - paths["configFolder"] = filepath.Join(base, "config") - paths["releaseFolder"] = filepath.Join(base, "dms3_release") - } - - return paths - } // ConfirmReleaseFolder checks for the existence of the release folder +// func ConfirmReleaseFolder(releasePath string) { if !dms3libs.IsFile(releasePath) { @@ -149,6 +135,8 @@ func InstallClientComponents(releasePath string) { var ssh *easyssh.MakeConfig + fmt.Println("Installing dms3client components onto remote device(s) identified in dms3build.toml...") + for _, client := range BuildConfig.Clients { ssh = &easyssh.MakeConfig{ @@ -160,23 +148,20 @@ func InstallClientComponents(releasePath string) { // copy dms3 release folder components to remote device platform remoteMkDir(ssh, "dms3_release") + remoteCopyDir(ssh, filepath.Join(releasePath, "config", "dms3client"), filepath.Join("dms3_release", "config", "dms3client")) + remoteCopyDir(ssh, filepath.Join(releasePath, "config", "dms3libs"), filepath.Join("dms3_release", "config", "dms3libs")) + remoteCopyDir(ssh, filepath.Join(releasePath, "config", "dms3mail"), filepath.Join("dms3_release", "config", "dms3mail")) + remoteCopyDir(ssh, filepath.Join(releasePath, "config", "dms3dashboard"), filepath.Join("dms3_release", "config", "dms3dashboard")) - remoteCopyDir(ssh, filepath.Join(releasePath, "dms3client"), filepath.Join("dms3_release", "dms3client")) - remoteCopyDir(ssh, filepath.Join(releasePath, "dms3libs"), filepath.Join("dms3_release", "dms3libs")) - remoteCopyDir(ssh, filepath.Join(releasePath, "dms3mail"), filepath.Join("dms3_release", "dms3mail")) - - remoteMkDir(ssh, filepath.Join("dms3_release", "dms3dashboard")) - remoteCopyFile(ssh, filepath.Join(filepath.Join(releasePath, "dms3dashboard"), "dms3dashboard.toml"), filepath.Join(filepath.Join("dms3_release", "dms3dashboard"), "dms3dashboard.toml")) - - remoteCopyDir(ssh, filepath.Join(filepath.Join(releasePath, BuildEnv[client.Platform].dirName), "go_dms3client"), filepath.Join("dms3_release", "go_dms3client")) - remoteCopyDir(ssh, filepath.Join(filepath.Join(releasePath, BuildEnv[client.Platform].dirName), "go_dms3mail"), filepath.Join("dms3_release", "go_dms3mail")) - remoteCopyDir(ssh, filepath.Join(filepath.Join(releasePath, BuildEnv[client.Platform].dirName), "dms3client_remote_installer"), "dms3client_remote_installer") - remoteRunCommand(ssh, "chmod +x dms3client_remote_installer") + // copy dms3 release file components to remote device platform + remoteMkDir(ssh, filepath.Join("dms3_release", "cmd")) + remoteCopyFile(ssh, filepath.Join(releasePath, "cmd", BuildEnv[client.Platform].dirName, "dms3client"), filepath.Join("dms3_release", "cmd", "dms3client")) + remoteCopyFile(ssh, filepath.Join(releasePath, "cmd", BuildEnv[client.Platform].dirName, "dms3mail"), filepath.Join("dms3_release", "cmd", "dms3mail")) + remoteCopyFile(ssh, filepath.Join(releasePath, "cmd", BuildEnv[client.Platform].dirName, "dms3client_remote_installer"), "dms3client_remote_installer") // run client installer, then remove on completion - remoteRunCommand(ssh, "echo '"+client.RemoteAdminPassword+"' | sudo -S ./dms3client_remote_installer") + remoteRunCommand(ssh, "echo "+client.RemoteAdminPassword+" | sudo -S "+"."+string(filepath.Separator)+"dms3client_remote_installer") remoteRunCommand(ssh, "rm dms3client_remote_installer") - fmt.Println("") } @@ -189,6 +174,8 @@ func InstallServerComponents(releasePath string) { var ssh *easyssh.MakeConfig + fmt.Println("Installing dms3server components onto remote device(s) identified in dms3build.toml...") + for _, server := range BuildConfig.Servers { ssh = &easyssh.MakeConfig{ @@ -200,36 +187,33 @@ func InstallServerComponents(releasePath string) { // copy dms3 release folder components to remote device platform remoteMkDir(ssh, "dms3_release") + remoteCopyDir(ssh, filepath.Join(releasePath, "config", "dms3server"), filepath.Join("dms3_release", "config", "dms3server")) + remoteCopyDir(ssh, filepath.Join(releasePath, "config", "dms3libs"), filepath.Join("dms3_release", "config", "dms3libs")) + remoteCopyDir(ssh, filepath.Join(releasePath, "config", "dms3dashboard"), filepath.Join("dms3_release", "config", "dms3dashboard")) - remoteCopyDir(ssh, filepath.Join(releasePath, "dms3server"), filepath.Join("dms3_release", "dms3server")) - remoteCopyDir(ssh, filepath.Join(releasePath, "dms3libs"), filepath.Join("dms3_release", "dms3libs")) - remoteCopyDir(ssh, filepath.Join(releasePath, "dms3dashboard"), filepath.Join("dms3_release", "dms3dashboard")) - - remoteCopyDir(ssh, filepath.Join(filepath.Join(releasePath, BuildEnv[server.Platform].dirName), "go_dms3server"), filepath.Join("dms3_release", "go_dms3server")) - remoteCopyDir(ssh, filepath.Join(filepath.Join(releasePath, BuildEnv[server.Platform].dirName), "dms3server_remote_installer"), "dms3server_remote_installer") - remoteRunCommand(ssh, "chmod +x dms3server_remote_installer") + remoteMkDir(ssh, filepath.Join("dms3_release", "cmd")) + remoteCopyFile(ssh, filepath.Join(filepath.Join(releasePath, "cmd", BuildEnv[server.Platform].dirName), "dms3server"), filepath.Join("dms3_release", "cmd", "dms3server")) + remoteCopyFile(ssh, filepath.Join(filepath.Join(releasePath, "cmd", BuildEnv[server.Platform].dirName), "dms3server_remote_installer"), "dms3server_remote_installer") // run server installer, then remove on completion - remoteRunCommand(ssh, "echo '"+server.RemoteAdminPassword+"' | sudo -S ./dms3server_remote_installer") + remoteRunCommand(ssh, "echo "+server.RemoteAdminPassword+" | sudo -S "+"."+string(filepath.Separator)+"dms3server_remote_installer") remoteRunCommand(ssh, "rm dms3server_remote_installer") - fmt.Println("") - } } // remoteMkDir creates a new folder over SSH with permissions passed in +// func remoteMkDir(ssh *easyssh.MakeConfig, newPath string) { - - _, _, _, err := ssh.Run("mkdir -p "+newPath, 5) - dms3libs.CheckErr(err) - + remoteRunCommand(ssh, "mkdir -p "+newPath) } // remoteCopyDir copies a directory over SSH from srcDir to destDir +// func remoteCopyDir(ssh *easyssh.MakeConfig, srcDir string, destDir string) { - fmt.Print("Copying folder " + srcDir + " to " + ssh.User + "@" + ssh.Server + ":" + destDir + "... ") + fmt.Println("Copying folder " + srcDir + " to " + ssh.User + "@" + ssh.Server + ":" + destDir + "...") + dirTree := dms3libs.WalkDir(srcDir) // create directory tree... @@ -242,55 +226,40 @@ func remoteCopyDir(ssh *easyssh.MakeConfig, srcDir string, destDir string) { } // ...then copy files into directory tree - for dirName, dirType := range dirTree { + for fileName, dirType := range dirTree { if dirType == 1 { - remoteCopyFile(ssh, dirName, destDir+dirName[len(srcDir):]) + remoteCopyFile(ssh, fileName, destDir+fileName[len(srcDir):]) } } - fmt.Println("Success") - } // remoteRunCommand runs a command via the SSH protocol +// func remoteRunCommand(ssh *easyssh.MakeConfig, command string) { - fmt.Print("Running command " + "'" + command + "' on " + ssh.User + "@" + ssh.Server + "... ") + fmt.Println("Running remote command " + "'" + command + "' on " + ssh.User + "@" + ssh.Server + "...") + _, _, _, err := ssh.Run(command, 5) dms3libs.CheckErr(err) - fmt.Println("Success") } -// remoteCopyFile copies a file from src to a remote dest using SCP +// remoteCopyFile copies a file from src to a remote dest file using SCP +// func remoteCopyFile(ssh *easyssh.MakeConfig, srcFile string, destFile string) { - fmt.Print("Copying file " + srcFile + " to " + destFile + " on " + ssh.User + "@" + ssh.Server + "... ") - err := ssh.Scp(srcFile, destFile) - dms3libs.CheckErr(err) - fmt.Println("Success") - -} - -// execFilePath returns the absolute path to the project root (default: go-distributed-motion-s3) -func execFilePath() string { - - ex, _ := os.Executable() - return filepath.Dir(ex) + fmt.Println("Copying file " + srcFile + " to " + ssh.User + "@" + ssh.Server + ":" + destFile + "...") -} - -// isRunningRelease checks if the executable was called from the dms3_release folder -func isRunningRelease() bool { - - dir, _ := filepath.Abs(execFilePath()) + srcAttrib, err := os.Stat(srcFile) + dms3libs.CheckErr(err) - if filepath.Base(filepath.Dir(dir)) == "dms3_release" { - return true - } + err = ssh.Scp(srcFile, destFile) + dms3libs.CheckErr(err) - return false + // ssh.Scp() does not set file attribs, so reset them on remote device + remoteRunCommand(ssh, "chmod 0"+strconv.FormatUint(uint64(srcAttrib.Mode()), 8)+" "+destFile) } diff --git a/dms3build/remote_installers/dms3client_remote_installer.go b/dms3build/remote_installers/dms3client_remote_installer.go deleted file mode 100644 index 82b00a6..0000000 --- a/dms3build/remote_installers/dms3client_remote_installer.go +++ /dev/null @@ -1,45 +0,0 @@ -// this script will be copied to the dms3 device component platform, executed, and -// then deleted automatically -// -// NOTE: must be run with admin privileges -// -package main - -import ( - "go-distributed-motion-s3/dms3libs" - "path/filepath" -) - -func main() { - - binaryInstallDir := "/usr/local/bin/" - configInstallDir := "/etc/distributed-motion-s3" - logDir := "/var/log/dms3" - - // stop existing systemd service (if running) - dms3libs.RunCommand("systemctl stop dms3client.service") - - // move binary files into binaryInstallDir - dms3libs.CopyFile("dms3_release/go_dms3client", filepath.Join(binaryInstallDir, "go_dms3client")) - _, err := dms3libs.RunCommand("chmod +x " + filepath.Join(binaryInstallDir, "go_dms3client")) - dms3libs.CheckErr(err) - - dms3libs.CopyFile("dms3_release/go_dms3mail", filepath.Join(binaryInstallDir, "go_dms3mail")) - _, err = dms3libs.RunCommand("chmod +x " + filepath.Join(binaryInstallDir, "go_dms3mail")) - dms3libs.CheckErr(err) - - // create log folder - dms3libs.MkDir(logDir) - - // copy configuration files into configInstallDir - dms3libs.MkDir(configInstallDir) - dms3libs.CopyDir("dms3_release/dms3client", configInstallDir) - dms3libs.CopyDir("dms3_release/dms3dashboard", configInstallDir) - dms3libs.CopyDir("dms3_release/dms3libs", configInstallDir) - dms3libs.CopyDir("dms3_release/dms3mail", configInstallDir) - dms3libs.RmDir("dms3_release") - - // restart systemd service - dms3libs.RunCommand("systemctl start dms3client.service") - -} diff --git a/dms3build/remote_installers/dms3server_remote_installer.go b/dms3build/remote_installers/dms3server_remote_installer.go deleted file mode 100644 index 73029c7..0000000 --- a/dms3build/remote_installers/dms3server_remote_installer.go +++ /dev/null @@ -1,40 +0,0 @@ -// this script will be copied to the dms3 device component platform, executed, and -// then deleted automatically -// -// NOTE: must be run with admin privileges -// -package main - -import ( - "go-distributed-motion-s3/dms3libs" - "path/filepath" -) - -func main() { - - binaryInstallDir := "/usr/local/bin/" - configInstallDir := "/etc/distributed-motion-s3" - logDir := "/var/log/dms3" - - // stop existing upstart service (if running) - dms3libs.RunCommand("service dms3server stop") - - // move binary files into binaryInstallDir - dms3libs.CopyFile("dms3_release/go_dms3server", filepath.Join(binaryInstallDir, "go_dms3server")) - _, err := dms3libs.RunCommand("chmod +x " + filepath.Join(binaryInstallDir, "go_dms3server")) - dms3libs.CheckErr(err) - - // create log folder - dms3libs.MkDir(logDir) - - // copy configuration files into configInstallDir - dms3libs.MkDir(configInstallDir) - dms3libs.CopyDir("dms3_release/dms3server", configInstallDir) - dms3libs.CopyDir("dms3_release/dms3dashboard", configInstallDir) - dms3libs.CopyDir("dms3_release/dms3libs", configInstallDir) - dms3libs.RmDir("dms3_release") - - // restart upstart service - dms3libs.RunCommand("service dms3server start") - -} diff --git a/dms3client/client_config.go b/dms3client/client_config.go index 630a46d..821ac16 100644 --- a/dms3client/client_config.go +++ b/dms3client/client_config.go @@ -3,12 +3,9 @@ package dms3client import ( - "go-distributed-motion-s3/dms3libs" - "time" + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) -var startTime time.Time - // clientConfig contains dms3Client configuration settings read from TOML file var clientConfig *structSettings diff --git a/dms3client/client_connector.go b/dms3client/client_connector.go index edec1a9..d64e396 100644 --- a/dms3client/client_connector.go +++ b/dms3client/client_connector.go @@ -4,50 +4,40 @@ package dms3client import ( "fmt" - "go-distributed-motion-s3/dms3dashboard" - "go-distributed-motion-s3/dms3libs" "net" "path/filepath" "strconv" "time" + + dms3dash "github.com/richbl/go-distributed-motion-s3/dms3dashboard" + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) // Init configs the library, configuration, and dashboard for dms3client +// func Init(configPath string) { - dms3libs.SetUptime(&startTime) + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) - dms3libs.LoadLibConfig(filepath.Join(configPath, "dms3libs/dms3libs.toml")) - dms3libs.LoadComponentConfig(&clientConfig, filepath.Join(configPath, "dms3client/dms3client.toml")) + dms3libs.LoadLibConfig(filepath.Join(configPath, "dms3libs", "dms3libs.toml")) + dms3libs.LoadComponentConfig(&clientConfig, filepath.Join(configPath, "dms3client", "dms3client.toml")) dms3libs.SetLogFileLocation(clientConfig.Logging) dms3libs.CreateLogger(clientConfig.Logging) - dms3dash.InitDashboardClient(configPath, configDashboardClientMetrics()) - startClient(clientConfig.Server.IP, clientConfig.Server.Port) - -} - -// configDashboardClientMetrics initializes the DeviceMetrics struct used by dms3dashboard -func configDashboardClientMetrics() *dms3dash.DeviceMetrics { - - dm := &dms3dash.DeviceMetrics{ - Platform: dms3dash.DevicePlatform{ - Type: dms3dash.Client, - }, - Period: dms3dash.DeviceTime{ - StartTime: startTime, - CheckInterval: clientConfig.Server.CheckInterval, - }, - } + dms3libs.LogInfo("dms3client started") - return dm + dms3dash.InitDashboardClient(configPath, clientConfig.Server.CheckInterval) + startClient(clientConfig.Server.IP, clientConfig.Server.Port) } // startClient periodically attempts to connect to the server (based on CheckInterval) +// func startClient(ServerIP string, ServerPort int) { + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + for { if conn, err := net.Dial("tcp", ServerIP+":"+fmt.Sprint(ServerPort)); err != nil { dms3libs.LogInfo(err.Error()) @@ -66,7 +56,7 @@ func startClient(ServerIP string, ServerPort int) { // func processClientRequest(conn net.Conn) { - dms3libs.LogDebug(dms3libs.GetFunctionName()) + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) dms3dash.ReceiveDashboardRequest(conn) receiveMotionDetectorState(conn) @@ -77,8 +67,11 @@ func processClientRequest(conn net.Conn) { } // receiveMotionDetectorState receives motion detector state from the server +// func receiveMotionDetectorState(conn net.Conn) { + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + buf := make([]byte, 8) // receive motion detector application state diff --git a/dms3client/client_manager.go b/dms3client/client_manager.go index 48d46e3..7aa6fcd 100644 --- a/dms3client/client_manager.go +++ b/dms3client/client_manager.go @@ -4,13 +4,16 @@ package dms3client import ( - "go-distributed-motion-s3/dms3libs" + "path/filepath" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) // ProcessMotionDetectorState starts/stops the motion detector application +// func ProcessMotionDetectorState() { - dms3libs.LogDebug(dms3libs.GetFunctionName()) + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) cmdStr := "" state := dms3libs.MotionDetector.State() diff --git a/dms3client/daemons/systemd/dms3client.service b/dms3client/daemons/systemd/dms3client.service index b896043..6d05d30 100644 --- a/dms3client/daemons/systemd/dms3client.service +++ b/dms3client/daemons/systemd/dms3client.service @@ -5,7 +5,7 @@ After=network.target [Service] Type=simple Restart=on-failure -ExecStart=/usr/local/bin/go_dms3client +ExecStart=/usr/local/bin/dms3client [Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/dms3dashboard/assets/css/paper-dashboard.css b/dms3dashboard/assets/css/paper-dashboard.css index 225a440..3aba981 100644 --- a/dms3dashboard/assets/css/paper-dashboard.css +++ b/dms3dashboard/assets/css/paper-dashboard.css @@ -12,7 +12,6 @@ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ - /* light colors - used for select dropdown */ p, @@ -29,11 +28,6 @@ p { line-height: 1.4em; } -p1 { - font-size: 16px; - line-height: 1.4em; -} - .icon-info { fill: #68B3C8; color: #68B3C8; @@ -63,6 +57,13 @@ body { font-family: 'Muli', Arial, sans-serif; } +img { + max-height: 70px; + max-width: 100%; + background-color: #ffffff; + padding:10px 0; +} + body .wrapper { min-height: 100vh; position: relative; @@ -153,10 +154,16 @@ hr { } .navbar-default { - background-color: #f4f3ef; + background-color: #ffffff; border-bottom: 1px solid #DDDDDD; } +.container-fluid > .navbar-header, +.container > .navbar-header { + margin-right: 0px; + margin-left: 0px; +} + .footer { background-attachment: fixed; position: relative; @@ -209,6 +216,8 @@ hr { .card .numbers { /* font-size: 2em; */ + font-size: 16px; + line-height: 1.4em; text-align: right; } diff --git a/dms3dashboard/assets/img/dms3logo.png b/dms3dashboard/assets/img/dms3logo.png new file mode 100644 index 0000000..637b9d9 Binary files /dev/null and b/dms3dashboard/assets/img/dms3logo.png differ diff --git a/dms3dashboard/assets/img/favicon.ico b/dms3dashboard/assets/img/favicon.ico deleted file mode 100644 index c87bcff..0000000 Binary files a/dms3dashboard/assets/img/favicon.ico and /dev/null differ diff --git a/dms3dashboard/assets/img/favicon.png b/dms3dashboard/assets/img/favicon.png new file mode 100644 index 0000000..afce3cb Binary files /dev/null and b/dms3dashboard/assets/img/favicon.png differ diff --git a/dms3dashboard/assets/img/favicon.svg b/dms3dashboard/assets/img/favicon.svg new file mode 100644 index 0000000..982d3bc --- /dev/null +++ b/dms3dashboard/assets/img/favicon.svg @@ -0,0 +1,56 @@ + + + + + + + + + + diff --git a/dms3dashboard/dashboard.html b/dms3dashboard/dashboard.html deleted file mode 100644 index 220c99d..0000000 --- a/dms3dashboard/dashboard.html +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - {{.Title}} - - - - - - - - - - - - - - - - - - -
-
- - - -
-
- - {{range $index, $_ := .Clients}} - - {{if eq (ModVal $index 4) 0}}
{{end}} - -
-
-
-
-
-
- -
-
-
-
-

{{.Platform.Hostname}}

- {{.Platform.Environment}} -
- {{.Platform.Kernel}} -
-
-
- -
-
-
- - {{if eq (ModVal $index 4) 3}}
{{end}} - - {{end}} - -
- -
-
-
- - - - - - - \ No newline at end of file diff --git a/dms3dashboard/dashboard_client.go b/dms3dashboard/dashboard_client.go index 47ae19f..031a1f9 100644 --- a/dms3dashboard/dashboard_client.go +++ b/dms3dashboard/dashboard_client.go @@ -1,69 +1,59 @@ -// Package dms3dash client implements a dms3server-based metrics dashboard for all dms3clients +// Package dms3dash client implements client services for a dms3server-based metrics dashboard // package dms3dash import ( "bytes" "encoding/gob" - "go-distributed-motion-s3/dms3libs" - "log" "net" "path/filepath" "time" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) var dashboardClientMetrics *DeviceMetrics // InitDashboardClient loads configuration and assigns the dashboard client profile (sets -// static client metrics) +// static client metrics) // -func InitDashboardClient(configPath string, dm *DeviceMetrics) { + +func InitDashboardClient(configPath string, checkInterval int) { + + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) dashboardConfig = new(tomlTables) - dms3libs.LoadComponentConfig(&dashboardConfig, filepath.Join(configPath, "dms3dashboard/dms3dashboard.toml")) + dms3libs.LoadComponentConfig(&dashboardConfig, filepath.Join(configPath, "dms3dashboard", "dms3dashboard.toml")) dashboardClientMetrics = &DeviceMetrics{ Platform: DevicePlatform{ - Type: dm.Platform.Type, - Hostname: dms3libs.DeviceHostname(), - Environment: dms3libs.DeviceOS() + " " + dms3libs.DevicePlatform(), - Kernel: dms3libs.DeviceKernel(), + Type: Client, + OSName: dms3libs.GetDeviceOSName(), + Hostname: dms3libs.GetDeviceHostname(), + Environment: dms3libs.GetDeviceDetails(dms3libs.Sysname) + " " + dms3libs.GetDeviceDetails(dms3libs.Machine), + Kernel: dms3libs.GetDeviceDetails(dms3libs.Release), }, Period: DeviceTime{ - StartTime: dm.Period.StartTime, - CheckInterval: dm.Period.CheckInterval, + CheckInterval: checkInterval, + StartTime: time.Now(), + Uptime: "", + LastReport: time.Now(), }, + ShowEventCount: false, + EventCount: 0, } dashboardClientMetrics.checkImagesFolder() - } // ReceiveDashboardRequest receives server requests and returns data -func ReceiveDashboardRequest(conn net.Conn) { - - if receiveDashboardEnableState(conn) == true { - sendDashboardData(conn) - } - -} - -// checkImagesFolder confirms the location of the motion-triggered image/movie files managed by -// the motion detector application (if installed), and used in displaying client metrics in the -// dashboard // -func (dash *DeviceMetrics) checkImagesFolder() { - - if dms3libs.IsFile(dashboardConfig.Client.ImagesFolder) { - dashboardClientMetrics.ShowEventCount = true - } else { +func ReceiveDashboardRequest(conn net.Conn) { - if dashboardConfig.Client.ImagesFolder == "" { - dashboardClientMetrics.ShowEventCount = false - } else { - log.Fatalln("unable to find motion detector application images folder... check TOML configuration file") - } + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + if receiveDashboardEnableState(conn) { + sendDashboardData(conn) } } @@ -73,25 +63,31 @@ func (dash *DeviceMetrics) checkImagesFolder() { // func receiveDashboardEnableState(conn net.Conn) bool { + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + buf := make([]byte, 16) - n, err := conn.Read(buf) - if err != nil { + if n, err := conn.Read(buf); err != nil { dms3libs.LogFatal(err.Error()) + return false + } else { + val := string(buf[:n]) + dms3libs.LogInfo("Received dashboard enable state as: " + val) + return (val == "1") } - val := string(buf[:n]) - dms3libs.LogInfo("Received dashboard enable state as: " + val) - return (val == "1") - } // sendDashboardData sends dashboard info to server +// func sendDashboardData(conn net.Conn) { + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + // update client metrics dashboardClientMetrics.Period.LastReport = time.Now() dashboardClientMetrics.Period.Uptime = dms3libs.Uptime(dashboardClientMetrics.Period.StartTime) + dashboardClientMetrics.Platform.Kernel = dms3libs.GetDeviceDetails(dms3libs.Release) if dashboardClientMetrics.ShowEventCount { dashboardClientMetrics.EventCount = dms3libs.CountFilesInDir(dashboardConfig.Client.ImagesFolder) @@ -111,3 +107,25 @@ func sendDashboardData(conn net.Conn) { } } + +// checkImagesFolder confirms the location of the motion-triggered image/movie files managed by +// the motion detector application (if installed), and used in displaying client metrics in the +// dashboard +// +func (dash *DeviceMetrics) checkImagesFolder() { + + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + + if dms3libs.IsFile(dashboardConfig.Client.ImagesFolder) { + dashboardClientMetrics.ShowEventCount = true + } else { + + if dashboardConfig.Client.ImagesFolder == "" { + dashboardClientMetrics.ShowEventCount = false + } else { + dms3libs.LogFatal("unable to find motion detector application images folder... check TOML configuration file") + } + + } + +} diff --git a/dms3dashboard/dashboard_config.go b/dms3dashboard/dashboard_config.go index fb8d4ab..224a116 100644 --- a/dms3dashboard/dashboard_config.go +++ b/dms3dashboard/dashboard_config.go @@ -4,6 +4,8 @@ package dms3dash import "time" +var DashboardEnable bool + var dashboardConfig *tomlTables var dashboardData *deviceData @@ -20,17 +22,26 @@ type clientKeyValues struct { // serverKeyValues represents the k-v pairs in the TOML file type serverKeyValues struct { - Enable bool Port int Filename string FileLocation string Title string + ReSort bool + ServerFirst bool + DeviceStatus *serverDeviceStatus +} + +// serverDeviceStatus represents the device status cycle in the TOML file +type serverDeviceStatus struct { + Caution int + Danger int + Missing int } -// deviceData represents all client dashboard elements +// deviceData represents dashboard elements from all devices type deviceData struct { Title string - Clients []DeviceMetrics + Devices []DeviceMetrics } // DeviceMetrics represents device data presented on the dashboard @@ -41,9 +52,10 @@ type DeviceMetrics struct { EventCount int } -// DevicePlatform represents the physical device plattform environment +// DevicePlatform represents the physical device platform environment type DevicePlatform struct { - Type dashboardType + Type dashboardDeviceType + OSName string Hostname string Environment string Kernel string @@ -57,11 +69,11 @@ type DeviceTime struct { LastReport time.Time } -// dashboardType defines the dashboard device type -type dashboardType int +// dashboardDeviceType defines the dashboard device type +type dashboardDeviceType int -// states of the motion detector application +// types of DMS3 devices const ( - Client dashboardType = iota + Client dashboardDeviceType = iota Server ) diff --git a/dms3dashboard/dashboard_server.go b/dms3dashboard/dashboard_server.go index 419365a..c2b9ce9 100644 --- a/dms3dashboard/dashboard_server.go +++ b/dms3dashboard/dashboard_server.go @@ -6,158 +6,196 @@ import ( "bytes" "encoding/gob" "fmt" - "go-distributed-motion-s3/dms3libs" "html/template" - "log" "net" "net/http" - "path" "path/filepath" "sort" "strings" "time" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) // InitDashboardServer configs the library and server configuration for the dashboard -func InitDashboardServer(configPath string, dm *DeviceMetrics) { - - dashboardConfig = new(tomlTables) - dms3libs.LoadComponentConfig(&dashboardConfig, filepath.Join(configPath, "dms3dashboard/dms3dashboard.toml")) +// - if dashboardConfig.Server.Enable == true { - dashboardConfig.Server.setDashboardFileLocation(configPath) - dashboardData = new(deviceData) - dm.appendServerMetrics() - go dashboardConfig.Server.startDashboard(configPath) - } +func InitDashboardServer(configPath string, checkInterval int) { -} + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) -// SendDashboardRequest manages dashboard requests and receipt of client device data -func SendDashboardRequest(conn net.Conn) { + dashboardConfig = new(tomlTables) + dms3libs.LoadComponentConfig(&dashboardConfig, filepath.Join(configPath, "dms3dashboard", "dms3dashboard.toml")) + dms3libs.CheckFileLocation(configPath, "dms3dashboard", &dashboardConfig.Server.FileLocation, dashboardConfig.Server.Filename) - dashboardConfig.Server.sendDashboardEnableState(conn) + dashboardData = &deviceData{ + Title: "", + Devices: []DeviceMetrics{}, + } - if dashboardConfig.Server.Enable == true { - dashboardConfig.Server.receiveDashboardData(conn) + // create initial server device entry in set of all dashboard devices + serverData := &DeviceMetrics{ + Platform: DevicePlatform{ + Type: Server, + OSName: dms3libs.GetDeviceOSName(), + Hostname: dms3libs.GetDeviceHostname(), + Environment: dms3libs.GetDeviceDetails(dms3libs.Sysname) + " " + dms3libs.GetDeviceDetails(dms3libs.Machine), + Kernel: dms3libs.GetDeviceDetails(dms3libs.Release), + }, + Period: DeviceTime{ + CheckInterval: checkInterval, + StartTime: time.Now(), + Uptime: "", + LastReport: time.Now(), + }, + ShowEventCount: false, + EventCount: 0, } + dashboardData.Devices = append(dashboardData.Devices, *serverData) + go dashboardConfig.Server.startDashboard(configPath) } -// setDashboardFileLocation sets the location of the HTML file used when displaying the dashboard -func (dash *serverKeyValues) setDashboardFileLocation(configPath string) { - - relPath := filepath.Join(configPath, "dms3dashboard") - devPath := filepath.Join(path.Dir(dms3libs.GetPackageDir()), "dms3dashboard") - fail := false - - if !dms3libs.IsFile(filepath.Join(dash.FileLocation, dash.Filename)) { - - // if no location set, set to release folder, else set to development folder - if dash.FileLocation == "" { - - if dms3libs.IsFile(filepath.Join(relPath, dash.Filename)) { - dash.FileLocation = relPath - } else if dms3libs.IsFile(filepath.Join(devPath, dash.Filename)) { - dash.FileLocation = devPath - } else { - fail = true - } +// SendDashboardRequest manages dashboard requests and receipt of client device data +// +func SendDashboardRequest(conn net.Conn) { - } else { - fail = true - } + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) - if fail { - log.Fatalln("unable to set dashboard location... check TOML configuration file") - } + if DashboardEnable { + sendDashboardEnableState(conn, "1") + receiveDashboardData(conn) + } else { + sendDashboardEnableState(conn, "0") } } -// startDashboard intializes and starts an HTTP server, serving the client dash on the server +// startDashboard initializes and starts an HTTP server, serving the client dash on the server +// func (dash *serverKeyValues) startDashboard(configPath string) { + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + + // needed for HTML template functionality funcs := template.FuncMap{ "ModVal": dms3libs.ModVal, "FormatDateTime": dms3libs.FormatDateTime, "iconStatus": iconStatus, "iconType": iconType, + "deviceOSName": deviceOSName, "clientCount": clientCount, "showEventCount": showEventCount, } tmpl := template.Must(template.New(dash.Filename).Funcs(funcs).ParseFiles(filepath.Join(dash.FileLocation, dash.Filename))) - http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir(filepath.Join(configPath, "dms3dashboard/assets"))))) + http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir(filepath.Join(configPath, "dms3dashboard", "assets"))))) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { dashboardData = &deviceData{ Title: dash.Title, - Clients: dashboardData.Clients, + Devices: dashboardData.Devices, } dashboardData.updateServerMetrics() - tmpl.Execute(w, dashboardData) + + if err := tmpl.Execute(w, dashboardData); err != nil { + dms3libs.LogFatal(err.Error()) + } }) - http.ListenAndServe(":"+fmt.Sprint(dash.Port), nil) + if err := http.ListenAndServe(":"+fmt.Sprint(dash.Port), nil); err != nil { + dms3libs.LogFatal(err.Error()) + } + } -// updateServerMetrics updates dynamic dashboard data of the server +// updateServerMetrics updates dynamic dashboard data of the server, triggered +// initially on dashboard start and subsequent webpage refreshes +// func (dd *deviceData) updateServerMetrics() { - dd.Clients[0].Period.LastReport = time.Now() - dd.Clients[0].Period.Uptime = dms3libs.Uptime(dd.Clients[0].Period.StartTime) + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) -} + for i := range dd.Devices { -// sendDashboardEnableState asks clients to send client info based on dashboard state -func (dash *serverKeyValues) sendDashboardEnableState(conn net.Conn) { + if dd.Devices[i].Platform.Type == Server { + dd.Devices[i].Period.LastReport = time.Now() + dd.Devices[i].Period.Uptime = dms3libs.Uptime(dd.Devices[i].Period.StartTime) + dd.Devices[i].Platform.Kernel = dms3libs.GetDeviceDetails(dms3libs.Release) + } else { + // check for and remove dead (non-reporting) client devices + lastUpdate := dms3libs.SecondsSince(dd.Devices[i].Period.LastReport) + missingDeviceLimit := uint32((dd.Devices[i].Period.CheckInterval * dashboardConfig.Server.DeviceStatus.Missing)) + + if lastUpdate > missingDeviceLimit { + dms3libs.LogInfo("Non-reporting remote device timeout reached: removing " + dd.Devices[i].Platform.Hostname + " client") + dd.Devices = append(dd.Devices[:i], dd.Devices[i+1:]...) + break + } - state := "0" + } - if dash.Enable { - state = "1" } - if _, err := conn.Write([]byte(state)); err != nil { +} + +// sendDashboardEnableState asks clients to send client info based on dashboard state +// +func sendDashboardEnableState(conn net.Conn, enableState string) { + + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + + if _, err := conn.Write([]byte(enableState)); err != nil { dms3libs.LogFatal(err.Error()) } else { - dms3libs.LogInfo("Sent dashboard enable state as: " + state) + dms3libs.LogInfo("Sent dashboard enable state as: " + enableState) } + } // receiveDashboardData receives and parses client dashboard metrics -func (dash *serverKeyValues) receiveDashboardData(conn net.Conn) { +// +func receiveDashboardData(conn net.Conn) { + + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) - newClientMetrics := new(DeviceMetrics) + updatedDeviceMetrics := new(DeviceMetrics) buf := make([]byte, 1024) if n, err := conn.Read(buf); err != nil { dms3libs.LogFatal(err.Error()) } else { - // gob decoding of client metrics - decBuf := bytes.NewBuffer(buf[:n]) - err = gob.NewDecoder(decBuf).Decode(newClientMetrics) - newClientMetrics.appendClientMetrics() + + decBuf := bytes.NewBuffer(buf[:n]) // gob decoding of client metrics + + if err := gob.NewDecoder(decBuf).Decode(updatedDeviceMetrics); err != nil { + dms3libs.LogFatal(err.Error()) + } + + updatedDeviceMetrics.updateDeviceMetrics() } } -// appendClientMetrics adds new clients to the dashboard list, or updates existing client +// updateDeviceMetrics adds new devices to the dashboard list, or updates existing device // metrics, where Hostname is the unique key // -func (dm *DeviceMetrics) appendClientMetrics() { +func (udm *DeviceMetrics) updateDeviceMetrics() { + + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) - for i := range dashboardData.Clients { + // scan for existing client device + for i := range dashboardData.Devices { - if dashboardData.Clients[i].Platform.Type == Client { + if dashboardData.Devices[i].Platform.Hostname == udm.Platform.Hostname { - if dashboardData.Clients[i].Platform.Hostname == dm.Platform.Hostname { - dashboardData.Clients[i].EventCount = dm.EventCount - dashboardData.Clients[i].Period.LastReport = dm.Period.LastReport - dashboardData.Clients[i].Period.Uptime = dm.Period.Uptime + if dashboardData.Devices[i].Platform.Type == Client { + dashboardData.Devices[i].EventCount = udm.EventCount + dashboardData.Devices[i].Period.LastReport = udm.Period.LastReport + dashboardData.Devices[i].Period.Uptime = udm.Period.Uptime + dashboardData.Devices[i].Platform.Kernel = udm.Platform.Kernel return } @@ -165,32 +203,48 @@ func (dm *DeviceMetrics) appendClientMetrics() { } - dashboardData.Clients = append(dashboardData.Clients, *dm) + // add new client device and (optionally) resort device order + dashboardData.Devices = append(dashboardData.Devices, *udm) - // resort clients alphabetically - sort.Slice(dashboardData.Clients, func(i, j int) bool { - switch strings.Compare(dashboardData.Clients[i].Platform.Hostname, dashboardData.Clients[j].Platform.Hostname) { + if dashboardConfig.Server.ReSort { + resortDashboardDevices() + } + +} + +// resortDashboardDevices re-sorts all dashboard devices alphabetically +// +func resortDashboardDevices() { + + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + + sort.Slice(dashboardData.Devices, func(i, j int) bool { + + switch strings.Compare(dashboardData.Devices[i].Platform.Hostname, dashboardData.Devices[j].Platform.Hostname) { case -1: return true case 1: return false } - return dashboardData.Clients[i].Platform.Hostname > dashboardData.Clients[j].Platform.Hostname + + return dashboardData.Devices[i].Platform.Hostname > dashboardData.Devices[j].Platform.Hostname + }) -} + if dashboardConfig.Server.ServerFirst { -// appendServerMetrics appends the server to the dashboard list -func (dm *DeviceMetrics) appendServerMetrics() { + // place server in first dashboard position + for i := range dashboardData.Devices { - serverData := new(DeviceMetrics) - *serverData = *dm - serverData.Platform.Type = Server - serverData.Platform.Hostname = dms3libs.DeviceHostname() - serverData.Platform.Environment = dms3libs.DeviceOS() + " " + dms3libs.DevicePlatform() - serverData.Platform.Kernel = dms3libs.DeviceKernel() + if dashboardData.Devices[i].Platform.Type == Server { + server := dashboardData.Devices[i] + dashboardData.Devices = append(dashboardData.Devices[:i], dashboardData.Devices[i+1:]...) + dashboardData.Devices = append([]DeviceMetrics{server}, dashboardData.Devices[:]...) + } + + } - dashboardData.Clients = append(dashboardData.Clients, *serverData) + } } @@ -200,17 +254,20 @@ func (dm *DeviceMetrics) appendServerMetrics() { // func iconStatus(index int) string { - seconds := dms3libs.SecondsSince(dashboardData.Clients[index].Period.LastReport) - interval := dashboardData.Clients[index].Period.CheckInterval - warningLimit := uint32((interval * 2)) - dangerLimit := uint32((interval * 4)) + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + + lastUpdate := dms3libs.SecondsSince(dashboardData.Devices[index].Period.LastReport) + checkInterval := dashboardData.Devices[index].Period.CheckInterval + + warningLimit := uint32((checkInterval * dashboardConfig.Server.DeviceStatus.Caution)) + dangerLimit := uint32((checkInterval * dashboardConfig.Server.DeviceStatus.Danger)) switch { - case seconds < warningLimit: + case lastUpdate < warningLimit: return "icon-success" - case (seconds >= warningLimit) && (seconds < dangerLimit): + case (lastUpdate >= warningLimit) && (lastUpdate < dangerLimit): return "icon-warning" - case seconds >= dangerLimit: + case lastUpdate >= dangerLimit: return "icon-danger" default: return "" @@ -219,9 +276,12 @@ func iconStatus(index int) string { } // iconType is an HTML template function that returns an icon based on device type +// func iconType(index int) string { - switch dashboardData.Clients[index].Platform.Type { + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + + switch dashboardData.Devices[index].Platform.Type { case Client: return "icon-raspberry-pi" case Server: @@ -232,14 +292,39 @@ func iconType(index int) string { } +// deviceType is an HTML template function that returns a string based on device type +// +// func deviceType(index int) string { + +// dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + +// switch dashboardData.Devices[index].Platform.Type { +// case Client: +// return "client" +// case Server: +// return "server" +// default: +// return "" +// } + +// } + +// deviceOSName is an HTML template function that returns a string based on device OS +// +func deviceOSName(index int) string { + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) + return dashboardData.Devices[index].Platform.OSName +} + // clientCount is an HTML template function that returns the current count of dms3clients // reporting to the server // func clientCount() int { - return len(dashboardData.Clients) - 1 + return len(dashboardData.Devices) - 1 } // showEventCount is an HTML template function that returns whether to display client event count +// func showEventCount(index int) bool { - return dashboardData.Clients[index].ShowEventCount + return dashboardData.Devices[index].ShowEventCount } diff --git a/dms3dashboard/dms3dashboard.html b/dms3dashboard/dms3dashboard.html new file mode 100644 index 0000000..3a03bc9 --- /dev/null +++ b/dms3dashboard/dms3dashboard.html @@ -0,0 +1,99 @@ + + + + + + + + + + + + {{.Title}} + + + + + + + + + + + + + + + +
+
+ + + +
+
+ + {{range $index, $_ := .Devices}} + +
+
+
+
+
+
+ +
+
+
+
+

{{.Platform.Hostname}}

+ {{deviceOSName $index}} +
+ {{.Platform.Environment}} +
+ {{.Platform.Kernel}} +
+
+
+ + +
+
+
+ + {{end}} + +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/dms3libs/lib_audio.go b/dms3libs/lib_audio.go index 897fc8d..70779a9 100644 --- a/dms3libs/lib_audio.go +++ b/dms3libs/lib_audio.go @@ -3,6 +3,7 @@ package dms3libs // PlayAudio uses the shell aplay command (system default) to play audioFile +// func PlayAudio(audioFile string) { if _, err := RunCommand(LibConfig.SysCommands["APLAY"] + " -q " + audioFile); err != nil { diff --git a/dms3libs/lib_config.go b/dms3libs/lib_config.go index 0aa90e0..75790e3 100644 --- a/dms3libs/lib_config.go +++ b/dms3libs/lib_config.go @@ -3,7 +3,8 @@ package dms3libs import ( - "log" + "errors" + "io/fs" "os" "path" @@ -20,53 +21,54 @@ type structConfig struct { // mapSysCommands provides a location mapping of required system commands type mapSysCommands map[string]string -// LoadLibConfig loads a TOML configuration file and parses entries into parameter values +// LoadLibConfig loads a TOML configuration file of system commands into parameter values +// func LoadLibConfig(configFile string) { - if !IsFile(configFile) { - log.Fatalln(configFile + " file not found") - } - - if _, err := toml.DecodeFile(configFile, &LibConfig); err != nil { - log.Fatalln(err.Error()) + if IsFile(configFile) { + if _, error := toml.DecodeFile(configFile, &LibConfig); error != nil { + LogFatal(error.Error()) + } + } else { + LogFatal(configFile + " file not found") } } -// LoadComponentConfig loads a TOML configuration file and parses entries into parameter values +// LoadComponentConfig loads a TOML configuration file of client/server configs into parameter values +// func LoadComponentConfig(structConfig interface{}, configFile string) { - if _, err := os.Stat(configFile); os.IsNotExist(err) { - log.Fatalln(configFile + " file not found") - } else if err != nil { - log.Fatalln(err.Error()) - } + if _, error := os.Stat(configFile); error == nil { + + if _, error := toml.DecodeFile(configFile, structConfig); error != nil { + LogFatal(error.Error()) + } + + } else { + + if errors.Is(error, fs.ErrNotExist) { + LogFatal(configFile + " file not found") + } else { + LogFatal(error.Error()) + } - if _, err := toml.DecodeFile(configFile, structConfig); err != nil { - log.Fatalln(err.Error()) } } // SetLogFileLocation sets the location of the log file based on TOML configuration +// func SetLogFileLocation(config *StructLogging) { projectDir := path.Dir(GetPackageDir()) - fail := false - // if no config location set, attempt to set to development project folder - if config.LogLocation == "" { - if IsFile(projectDir) { + if !IsFile(config.LogLocation) { + if config.LogLocation == "" && IsFile(projectDir) { // if no config location set, set to development project folder config.LogLocation = projectDir } else { - fail = true + LogFatal("unable to set log location... check TOML configuration file") } - } else if !IsFile(config.LogLocation) { - fail = true - } - - if fail { - log.Fatalln("unable to set log location... check TOML configuration file") } } diff --git a/dms3libs/lib_detector_config.go b/dms3libs/lib_detector_config.go index 8baa6f7..e1b2504 100644 --- a/dms3libs/lib_detector_config.go +++ b/dms3libs/lib_detector_config.go @@ -3,23 +3,40 @@ // package dms3libs -// MotionDetector is the motion detector application run on the clients +// MotionDetector is the motion detector application running on the clients (e.g., motion) var MotionDetector = structMotionDetector{ command: "motion", state: Stop, } +// states of the motion detector application +const ( + Stop MotionDetectorState = iota + Start +) + +type structMotionDetector struct { + command string + state MotionDetectorState +} + +// MotionDetectorState defines the motion detector application state type +type MotionDetectorState int + // Command returns the motion detector application command +// func (s structMotionDetector) Command() string { return s.command } // State returns the motion detector application state +// func (s structMotionDetector) State() MotionDetectorState { return s.state } // SetState sets the motion detector application state +// func (s *structMotionDetector) SetState(state MotionDetectorState) bool { switch state { @@ -33,17 +50,3 @@ func (s *structMotionDetector) SetState(state MotionDetectorState) bool { } } - -// states of the motion detector application -const ( - Stop MotionDetectorState = iota - Start -) - -type structMotionDetector struct { - command string - state MotionDetectorState -} - -// MotionDetectorState defines the motion detector application state type -type MotionDetectorState int diff --git a/dms3libs/lib_file.go b/dms3libs/lib_file.go index d2ed588..cc49293 100644 --- a/dms3libs/lib_file.go +++ b/dms3libs/lib_file.go @@ -3,23 +3,24 @@ package dms3libs import ( + "errors" "io" + "io/fs" "os" "path/filepath" ) // IsFile returns true/false on existence of file/folder passed in +// func IsFile(filename string) bool { - if _, err := os.Stat(filename); !os.IsNotExist(err) { - return true - } - - return false + _, error := os.Stat(filename) + return (!errors.Is(error, fs.ErrNotExist)) } // MkDir creates a new folder with permissions passed in +// func MkDir(newPath string) { error := os.MkdirAll(newPath, os.ModePerm) @@ -28,20 +29,22 @@ func MkDir(newPath string) { } // RmDir removes the folder passed in +// func RmDir(dir string) { if IsFile(dir) { - err := os.RemoveAll(dir) - CheckErr(err) + error := os.RemoveAll(dir) + CheckErr(error) } } // WalkDir generates a map of directories (0) and files (1) +// func WalkDir(dirname string) map[string]int { fileList := map[string]int{} - error := filepath.Walk(dirname, func(path string, f os.FileInfo, err error) error { + error := filepath.WalkDir(dirname, func(path string, f os.DirEntry, err error) error { // exclude root directory if f.IsDir() && f.Name() == dirname { @@ -63,6 +66,7 @@ func WalkDir(dirname string) map[string]int { } // CopyFile copies a file from src to dest +// func CopyFile(src string, dest string) { srcFile, err := os.Open(src) @@ -79,9 +83,16 @@ func CopyFile(src string, dest string) { err = destFile.Sync() CheckErr(err) + srcAttrib, err := os.Stat(src) + CheckErr(err) + + err = os.Chmod(dest, srcAttrib.Mode()) + CheckErr(err) + } // CopyDir copies a directory from srcDir to destDir +// func CopyDir(srcDir string, destDir string) { pathRoot := filepath.Dir(srcDir) @@ -113,6 +124,7 @@ func CopyDir(srcDir string, destDir string) { } // CountFilesInDir recursively counts the files in the dir passed in +// func CountFilesInDir(srcDir string) int { fileCount := 0 @@ -129,3 +141,19 @@ func CountFilesInDir(srcDir string) int { return fileCount } + +// CheckFileLocation checks/sets the location of file and pathname passed in as defined in various +// TOML config files, returning a fully qualified path +// +func CheckFileLocation(configPath string, fileDir string, fileLocation *string, filename string) { + + // set default template location + if *fileLocation == "" { + *fileLocation = filepath.Join(configPath, fileDir) + } + + if filename == "" || !IsFile(filepath.Join(*fileLocation, filename)) { + LogFatal("unable to set file location... check TOML configuration file") + } + +} diff --git a/dms3libs/lib_log.go b/dms3libs/lib_log.go index 653b215..2bf8bc9 100644 --- a/dms3libs/lib_log.go +++ b/dms3libs/lib_log.go @@ -50,7 +50,7 @@ func CreateLogger(logger *StructLogging) { case 1: { if f, err = os.OpenFile(filepath.Join(logger.LogLocation, logger.LogFilename), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644); err != nil { - log.Fatalln(err) + LogFatal(err.Error()) } } } diff --git a/dms3libs/lib_network.go b/dms3libs/lib_network.go index 9a597e4..80cd571 100644 --- a/dms3libs/lib_network.go +++ b/dms3libs/lib_network.go @@ -3,6 +3,7 @@ package dms3libs import ( + "os/exec" "strconv" "sync" ) @@ -15,13 +16,13 @@ func PingHosts(ipBase string, ipRange []int) { var wg sync.WaitGroup cmd := LibConfig.SysCommands["PING"] + " -q -W 1 -c 1 " + ipBase - for i := ipRange[0]; i < ipRange[1]; i++ { + for i := ipRange[0]; i <= ipRange[1]; i++ { wg.Add(1) // allow threaded system command calls to finish asynchronously go func(i int, w *sync.WaitGroup) { defer w.Done() - RunCommand(cmd + strconv.Itoa(i)) + _, _ = RunCommand(cmd + strconv.Itoa(i)) }(i, &wg) } @@ -29,8 +30,8 @@ func PingHosts(ipBase string, ipRange []int) { } -// FindMacs uses arp to find mac addressed passed in, returning true if any mac passed in is found -// (e.g., mac1 | mac2 | mac3) +// FindMacs uses 'ip neigh' to find mac address(es) passed in, returning true if any mac passed in is found +// (e.g., mac1|mac2|mac3) // func FindMacs(macsToFind []string) bool { @@ -45,12 +46,19 @@ func FindMacs(macsToFind []string) bool { } - res, err := RunCommand(LibConfig.SysCommands["ARP"] + " -n | " + LibConfig.SysCommands["GREP"] + " -E '" + macListRegex + "'") + if _, err := RunCommand(LibConfig.SysCommands["IP"] + " neigh | " + LibConfig.SysCommands["GREP"] + " -iE '" + macListRegex + "'"); err != nil { - if err != nil { - LogInfo(LibConfig.SysCommands["ARP"] + " command code: " + err.Error()) - } + switch err.(type) { + case *exec.ExitError: // no ip found + LogInfo(LibConfig.SysCommands["IP"] + " command: no device mac address found") + default: // fatal command error + LogFatal("Failed to run '" + LibConfig.SysCommands["IP"] + " neigh | " + LibConfig.SysCommands["GREP"] + " -iE '" + macListRegex + "': " + err.Error()) + } - return (len(string(res)) > 0) + return false + } else { + LogInfo(LibConfig.SysCommands["IP"] + " command: device mac address found") + return true + } } diff --git a/dms3libs/lib_os.go b/dms3libs/lib_os.go index bec0f15..81c8cc5 100644 --- a/dms3libs/lib_os.go +++ b/dms3libs/lib_os.go @@ -3,13 +3,27 @@ package dms3libs import ( + "bufio" "os" - "runtime" + "path/filepath" + "regexp" + "strings" "syscall" ) -// DeviceHostname returns the name of the local machine -func DeviceHostname() string { +// DeviceDetails defines the set of available device details available in GetDeviceDetails +type DeviceDetails int + +// types of DMS3 devices +const ( + Sysname DeviceDetails = iota + Machine + Release +) + +// GetDeviceHostname returns the name of the local machine +// +func GetDeviceHostname() string { name, err := os.Hostname() CheckErr(err) @@ -17,18 +31,45 @@ func DeviceHostname() string { } -// DeviceOS returns the operating system of the local machine -func DeviceOS() string { - return runtime.GOOS -} +// GetDeviceOSName returns the OS release name (NAME) and version ID (VERSION_ID) from a parse of +// the /etc/os-release file found in most Linux-based distributions +// +func GetDeviceOSName() string { + + result := "OS unknown" + + if file, err := os.Open(filepath.Join(string(filepath.Separator), "etc", "os-release")); err == nil { + + defer file.Close() + scanner := bufio.NewScanner(file) -// DevicePlatform returns the CPU architecture of the local machine -func DevicePlatform() string { - return runtime.GOARCH + nameRegx := regexp.MustCompile(`^NAME=(.*)$`) + versionIDRegx := regexp.MustCompile(`^VERSION_ID=(.*)$`) + osName := "" + osVersion := "" + + for scanner.Scan() { + + if res := nameRegx.FindStringSubmatch(scanner.Text()); res != nil { + osName = strings.Trim(res[1], `"`) + } else if res := versionIDRegx.FindStringSubmatch(scanner.Text()); res != nil { + osVersion = strings.Trim(res[1], `"`) + } + + } + + if osName != "" && osVersion != "" { + result = strings.ToLower(osName + " " + osVersion) + } + + } + + return result } -// DeviceKernel returns the current kernel in use on the local machine -func DeviceKernel() string { +// GetDeviceDetails returns device details of the local machine +// +func GetDeviceDetails(element DeviceDetails) string { utsName, error := uname() CheckErr(error) @@ -36,14 +77,28 @@ func DeviceKernel() string { var len int var buf [65]byte - for ; utsName.Release[len] != 0; len++ { - buf[len] = uint8(utsName.Release[len]) + switch element { + case Sysname: + for ; utsName.Sysname[len] != 0; len++ { + buf[len] = uint8(utsName.Sysname[len]) + } + case Machine: + for ; utsName.Machine[len] != 0; len++ { + buf[len] = uint8(utsName.Machine[len]) + } + case Release: + for ; utsName.Release[len] != 0; len++ { + buf[len] = uint8(utsName.Release[len]) + } + default: + LogFatal("invalid DeviceDetails element passed in") } - return string(buf[:len]) + return strings.ToLower(string(buf[:len])) } // uname returns the Utsname struct used to query system settings +// func uname() (*syscall.Utsname, error) { uts := &syscall.Utsname{} diff --git a/dms3libs/lib_process.go b/dms3libs/lib_process.go index 564e16e..456d759 100644 --- a/dms3libs/lib_process.go +++ b/dms3libs/lib_process.go @@ -3,115 +3,81 @@ package dms3libs import ( - "os" "os/exec" - "strconv" - "strings" ) // RunCommand is a simple wrapper for the exec.Command() call +// // NOTE: this call is blocking (non-threaded), and will return only after the command // completes // func RunCommand(cmd string) (res []byte, err error) { - return exec.Command("bash", "-c", cmd).Output() + return exec.Command(LibConfig.SysCommands["BASH"], "-c", cmd).Output() } // IsRunning checks if application is currently running (has PID > 0) +// func IsRunning(application string) bool { - return (GetPID(application) > 0) -} - -// GetPIDCount returns the count of application PIDs -func GetPIDCount(application string) int { - - res, _ := RunCommand(LibConfig.SysCommands["PGREP"] + " -x -c " + application) - count, _ := strconv.Atoi(string(StripRet(res))) - return count - -} -// getPIDList returns application PIDs (0 if no process) -func getPIDList(application string) (int, []int) { + if _, err := RunCommand(LibConfig.SysCommands["PGREP"] + " -i " + application); err != nil { - pidCount := GetPIDCount(application) - - switch pidCount { - case 0: // no process running - return 0, []int{0} - default: // one or more processes running - { - res, _ := RunCommand(LibConfig.SysCommands["PGREP"] + " -x " + application) - strPIDs := strings.Split(string(StripRet(res)), "\n") - - PIDs := []int{} - for _, i := range strPIDs { - pid, _ := strconv.Atoi(i) - PIDs = append(PIDs, pid) - } - return pidCount, PIDs - } - } - -} - -// GetPID returns the application PID (0 if no process) -func GetPID(application string) int { - - pidCount, pidList := getPIDList(application) - - switch pidCount { - case 0, 1: - return pidList[0] - default: // >1 processes running - { - Fatal.Fatalln("multiple instances of " + application + " process running") - return 0 + switch err.(type) { + case *exec.ExitError: // no process found + LogInfo("Process not found when running '" + LibConfig.SysCommands["PGREP"] + " -i " + application) + default: // fatal command error + LogFatal("Failed to run '" + LibConfig.SysCommands["PGREP"] + " -i " + application + "': " + err.Error()) } + return false + } else { + return true } } // StartStopApplication enable/disables the application passed in +// func StartStopApplication(state MotionDetectorState, application string) bool { switch state { case Start: { + if IsRunning(application) { return false // already running } - _, err := RunCommand(application) - - if err != nil { - LogInfo("Failed to run " + application + ": " + err.Error()) + if _, err := RunCommand(application); err != nil { + LogInfo("Failed to start " + application + ": " + err.Error()) return false + } else { + return true } - return true } case Stop: { + if !IsRunning(application) { return false // already stopped } - // find application process and kill it - appPID := GetPID(application) - proc, err := os.FindProcess(appPID) + if _, err := RunCommand(LibConfig.SysCommands["PKILL"] + " -i " + application); err == nil { + return true + } else { - if err != nil { - LogInfo("unable to find PID") + switch err.(type) { + case *exec.ExitError: // no process matched + LogInfo("Process not found when running '" + LibConfig.SysCommands["PKILL"] + " -i " + application) + default: // fatal command error + LogFatal("Failed to run '" + LibConfig.SysCommands["PKILL"] + " -i " + application + "': " + err.Error()) + } return false - } - proc.Kill() - return true + } } default: { - LogInfo("Unanticipated motion detector state: ignored") + LogInfo("Unanticipated application state: ignored") return false } } diff --git a/dms3libs/lib_util.go b/dms3libs/lib_util.go index df38347..a66a941 100644 --- a/dms3libs/lib_util.go +++ b/dms3libs/lib_util.go @@ -4,6 +4,8 @@ package dms3libs import ( "fmt" + "image" + _ "image/jpeg" "log" "os" "path" @@ -13,6 +15,7 @@ import ( ) // GetFunctionName uses reflection (runtime) to return current function name +// func GetFunctionName() string { pc := make([]uintptr, 10) @@ -25,6 +28,7 @@ func GetFunctionName() string { } // GetPackageDir returns the absolute path of the calling package +// func GetPackageDir() string { _, filename, _, ok := runtime.Caller(1) @@ -38,6 +42,7 @@ func GetPackageDir() string { } // StripRet strips the rightmost byte from the byte array +// func StripRet(value []byte) []byte { if len(value) <= 1 { @@ -48,37 +53,44 @@ func StripRet(value []byte) []byte { } -// SetUptime sets the uptime for the application process -func SetUptime(startTime *time.Time) { - *startTime = time.Now() -} - // Uptime returns uptime for the application process +// func Uptime(startTime time.Time) string { return fmtDuration(time.Since(startTime)) } // SecondsSince returns seconds passed since value passed +// func SecondsSince(value time.Time) uint32 { return uint32(time.Since(value).Seconds()) } // To24H converts 12-hour time to 24-hour time, returning a string (e.g., "231305") +// func To24H(value time.Time) string { return value.Format("150405") } // Format24H formats 24-hour time to six places (HHMMSS) +// func Format24H(time string) string { return rightPadToLen(time, "0", 6) } // FormatDateTime formats time to "date at time" +// func FormatDateTime(value time.Time) string { return value.Format("2006-01-02 at 15:04:05") } +// ModVal returns the remainder of number/val passed in +// +func ModVal(number int, val int) int { + return number % val +} + // CheckErr does simple error management (no logging dependencies) +// func CheckErr(err error) { if err != nil { @@ -88,12 +100,43 @@ func CheckErr(err error) { } -// ModVal returns the remainder of number/val passed in -func ModVal(number int, val int) int { - return number % val +// FUTURE USE: GetGitVersion queries the external git runtime for the most recent version tag, returning a +// string to the caller +// +func GetGitVersion() string { + + if res, err := RunCommand("git tag --sort=-version:refname | head -n 1"); err != nil { + return "no git version" + } else { + return string(res[:len(res)-1]) + } + +} + +// GetImageDimensions returns the (width, height) of an image passed in +// +func GetImageDimensions(imagePath string) (int, int) { + + var file *os.File + var err error + var img image.Config + + if file, err = os.Open(imagePath); err != nil { + LogFatal(err.Error()) + } + + defer file.Close() + + if img, _, err = image.DecodeConfig(file); err != nil { + LogFatal(err.Error()) + } + + return img.Width, img.Height + } // rightPadToLen pads a string to pLen places with padStr +// func rightPadToLen(s string, padStr string, pLen int) string { return s + strings.Repeat(padStr, pLen-len(s)) } diff --git a/dms3libs/tests/lib_audio_test.go b/dms3libs/tests/lib_audio_test.go index da28cca..6408606 100644 --- a/dms3libs/tests/lib_audio_test.go +++ b/dms3libs/tests/lib_audio_test.go @@ -1,13 +1,15 @@ package dms3libs_test import ( - "go-distributed-motion-s3/dms3libs" - "go-distributed-motion-s3/dms3server" + "path/filepath" "testing" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" + "github.com/richbl/go-distributed-motion-s3/dms3server" ) func init() { - dms3libs.LoadLibConfig("../../config/dms3libs.toml") + dms3libs.LoadLibConfig(filepath.Join("..", "..", "config", "dms3libs.toml")) } func TestPlayAudio(t *testing.T) { @@ -25,13 +27,15 @@ func TestPlayAudio(t *testing.T) { func TestAudioConfig(t *testing.T) { - dms3libs.LoadComponentConfig(&dms3server.serverConfig, "../../config/dms3server.toml") + configPath := dms3libs.GetPackageDir() + + dms3libs.LoadComponentConfig(&dms3server.ServerConfig, filepath.Join(configPath, "..", "..", "config", "dms3server.toml")) - mediaFileStart := dms3server.serverConfig.Audio.PlayMotionStart - mediaFileStop := dms3server.serverConfig.Audio.PlayMotionStop + mediaFileStart := dms3server.ServerConfig.Audio.PlayMotionStart + mediaFileStop := dms3server.ServerConfig.Audio.PlayMotionStop if mediaFileStart == "" { - mediaFileStart = "../../dms3server/media/motion_start.wav" + mediaFileStart = filepath.Join("..", "..", "dms3server", "media", "motion_start.wav") } if dms3libs.IsFile(mediaFileStart) { @@ -42,7 +46,7 @@ func TestAudioConfig(t *testing.T) { } if mediaFileStop == "" { - mediaFileStop = "../../dms3server/media/motion_stop.wav" + mediaFileStop = filepath.Join("..", "..", "dms3server", "media", "motion_stop.wav") } if dms3libs.IsFile(mediaFileStop) { diff --git a/dms3libs/tests/lib_config_test.go b/dms3libs/tests/lib_config_test.go index d629207..f5a7348 100644 --- a/dms3libs/tests/lib_config_test.go +++ b/dms3libs/tests/lib_config_test.go @@ -1,12 +1,19 @@ package dms3libs_test import ( - "go-distributed-motion-s3/dms3libs" + "path/filepath" "testing" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) -func init() { - dms3libs.LoadLibConfig("../../config/dms3libs.toml") +func TestLoadLibConfig(t *testing.T) { + + libsLocation := filepath.Join("..", "..", "config", "dms3libs.toml") + + dms3libs.LoadLibConfig(libsLocation) + t.Log("libs configuration file loaded from", libsLocation, "successfully") + } func TestConfiguration(t *testing.T) { @@ -22,3 +29,34 @@ func TestConfiguration(t *testing.T) { } } + +func TestLoadComponentConfig(t *testing.T) { + + type structServer struct { + Port int + CheckInterval int + Logging *dms3libs.StructLogging + } + + type structSettings struct { + Server *structServer + } + + testSettings := new(structSettings) + configPath := dms3libs.GetPackageDir() + configLocation := filepath.Join("..", "..", "config", "dms3server.toml") + + dms3libs.LoadComponentConfig(&testSettings, filepath.Join(configPath, configLocation)) + t.Log("component configuration file loaded from", configLocation, "successfully") + +} + +func TestSetLogFileLocation(t *testing.T) { + + testSettings := new(dms3libs.StructLogging) + testSettings.LogLocation = "" + + dms3libs.SetLogFileLocation(testSettings) + t.Log("log location set to", testSettings.LogLocation, "successfully") + +} diff --git a/dms3libs/tests/lib_detector_config_test.go b/dms3libs/tests/lib_detector_config_test.go index 76efbf3..2a92f5c 100644 --- a/dms3libs/tests/lib_detector_config_test.go +++ b/dms3libs/tests/lib_detector_config_test.go @@ -1,36 +1,33 @@ package dms3libs_test import ( - "go-distributed-motion-s3/dms3libs" "testing" -) -func init() { - dms3libs.LoadLibConfig("../../config/dms3libs.toml") -} + "github.com/richbl/go-distributed-motion-s3/dms3libs" +) func TestCommand(t *testing.T) { - if dms3libs.MotionDetector.Command() != "motion" { - t.Error("command failed") + if dms3libs.MotionDetector.Command() == "" { + t.Error("function failed") } } -func TestSetState(t *testing.T) { +func TestState(t *testing.T) { - if !dms3libs.MotionDetector.SetState(dms3libs.Start) { - t.Error("command failed") + dms3libs.MotionDetector.SetState(dms3libs.Start) + + if dms3libs.MotionDetector.State() != dms3libs.Start { + t.Error("function failed") } } -func TestState(t *testing.T) { - - dms3libs.MotionDetector.SetState(dms3libs.Start) +func TestSetState(t *testing.T) { - if dms3libs.MotionDetector.State() != dms3libs.Start { - t.Error("command failed") + if !dms3libs.MotionDetector.SetState(dms3libs.Start) { + t.Error("function failed") } } diff --git a/dms3libs/tests/lib_file_test.go b/dms3libs/tests/lib_file_test.go index 681f6c5..d179df8 100644 --- a/dms3libs/tests/lib_file_test.go +++ b/dms3libs/tests/lib_file_test.go @@ -1,19 +1,16 @@ package dms3libs_test import ( - "go-distributed-motion-s3/dms3libs" "os" "path/filepath" "testing" -) -func init() { - dms3libs.LoadLibConfig("../../config/dms3libs.toml") -} + "github.com/richbl/go-distributed-motion-s3/dms3libs" +) func TestIsFile(t *testing.T) { - testFile := "lib_util_test.go" + testFile := "lib_file_test.go" if !dms3libs.IsFile(testFile) { t.Error(testFile + " file not found, but should have been") @@ -21,36 +18,27 @@ func TestIsFile(t *testing.T) { } -func TestCopyFile(t *testing.T) { - - dms3libs.CopyFile(filepath.Join(dms3libs.GetPackageDir(), "lib_audio_test.wav"), "tmpfile") - - if dms3libs.IsFile("tmpfile") { - os.Remove("tmpfile") - } else { - t.Error("file not found, but should have been") - } - -} - func TestMkDir(t *testing.T) { - dms3libs.MkDir(filepath.Join(dms3libs.GetPackageDir(), "tmpDir")) + newDir := filepath.Join(dms3libs.GetPackageDir(), "tmpDir") + dms3libs.MkDir(newDir) - if dms3libs.IsFile("tmpDir") { - os.Remove("tmpDir") + if dms3libs.IsFile(newDir) { + dms3libs.CheckErr(os.RemoveAll(newDir)) } else { - t.Error("directory not found, but should have been") + t.Error("directory " + newDir + " not found, but should have been") } } func TestRmDir(t *testing.T) { - dms3libs.MkDir(filepath.Join(dms3libs.GetPackageDir(), "tmpDir")) - dms3libs.RmDir(filepath.Join(dms3libs.GetPackageDir(), "tmpDir")) + newDir := filepath.Join(dms3libs.GetPackageDir(), "tmpDir") - if dms3libs.IsFile("tmpDir") { + dms3libs.MkDir(newDir) + dms3libs.RmDir(newDir) + + if dms3libs.IsFile(newDir) { t.Error("directory not removed, but should have been") } @@ -60,24 +48,45 @@ func TestWalkDir(t *testing.T) { dirCount := 0 fileCount := 0 - currentDir := dms3libs.GetPackageDir() - for _, dirType := range dms3libs.WalkDir(currentDir) { + newDir := filepath.Join(dms3libs.GetPackageDir(), "tmpDir") + newFile := "tmpFile" - switch dirType { - case 0: + dms3libs.MkDir(newDir) + dms3libs.CopyFile(filepath.Join(dms3libs.GetPackageDir(), "lib_audio_test.wav"), filepath.Join(newDir, newFile)) + + for _, dirType := range dms3libs.WalkDir(newDir) { + + if dirType == 0 { dirCount += 1 - case 1: + } else { fileCount += 1 } + } if dirCount != 1 { - t.Error("wrong directory count in", currentDir) + t.Error("wrong directory count in", newDir) } - if fileCount != 9 { - t.Error("wrong file count in", currentDir) + if fileCount != 1 { + t.Error("wrong file count in", newDir) + } + + // avoid using os.RemoveAll() in the event of an error with newDir creation + os.Remove(newDir + "/" + newFile) + os.Remove(newDir) + +} + +func TestCopyFile(t *testing.T) { + + dms3libs.CopyFile(filepath.Join(dms3libs.GetPackageDir(), "lib_audio_test.wav"), "tmpfile") + + if dms3libs.IsFile("tmpfile") { + os.Remove("tmpfile") + } else { + t.Error("file not found, but should have been") } } @@ -93,3 +102,38 @@ func TestCopyDir(t *testing.T) { } } + +func TestCountFilesInDir(t *testing.T) { + + dms3libs.MkDir(filepath.Join(dms3libs.GetPackageDir(), "tmpDir")) + dms3libs.CopyFile(filepath.Join(dms3libs.GetPackageDir(), "lib_audio_test.wav"), filepath.Join(dms3libs.GetPackageDir(), "tmpDir", "tmpFile")) + currentDir := filepath.Join(dms3libs.GetPackageDir(), "tmpDir") + + if dms3libs.CountFilesInDir(currentDir) != 1 { + t.Error("incorrect file count") + } + + dms3libs.RmDir(currentDir) + +} + +func TestCheckFileLocation(t *testing.T) { + + dms3libs.MkDir(filepath.Join(dms3libs.GetPackageDir(), "tmpDir")) + dms3libs.CopyFile(filepath.Join(dms3libs.GetPackageDir(), "lib_audio_test.wav"), filepath.Join(dms3libs.GetPackageDir(), "tmpDir", "tmpFile")) + currentDir := filepath.Join(dms3libs.GetPackageDir(), "tmpDir") + + configPath := dms3libs.GetPackageDir() + fileDir := "tmpDir" + fileLocation := "" + filename := "tmpFile" + + dms3libs.CheckFileLocation(configPath, fileDir, &fileLocation, filename) + + if fileLocation == "" { + t.Error("fileLocation not set") + } + + dms3libs.RmDir(currentDir) + +} diff --git a/dms3libs/tests/lib_log_test.go b/dms3libs/tests/lib_log_test.go index 022c8e2..969c265 100644 --- a/dms3libs/tests/lib_log_test.go +++ b/dms3libs/tests/lib_log_test.go @@ -1,9 +1,10 @@ package dms3libs_test import ( - "go-distributed-motion-s3/dms3libs" "os" "testing" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) func TestCreateLogger(t *testing.T) { diff --git a/dms3libs/tests/lib_network_test.go b/dms3libs/tests/lib_network_test.go index d230584..61e73b6 100644 --- a/dms3libs/tests/lib_network_test.go +++ b/dms3libs/tests/lib_network_test.go @@ -1,13 +1,15 @@ package dms3libs_test import ( - "go-distributed-motion-s3/dms3libs" "os/exec" + "path/filepath" "testing" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) func init() { - dms3libs.LoadLibConfig("../../config/dms3libs.toml") + dms3libs.LoadLibConfig(filepath.Join("..", "..", "config", "dms3libs.toml")) } func TestPingHosts(t *testing.T) { @@ -23,13 +25,14 @@ func TestPingHosts(t *testing.T) { func TestFindMacs(t *testing.T) { // ACTION: set active network interface (e.g., eth0) - curInterface := "wlp2s0" + curInterface := "wlp9s0" var localMAC []string // determine local MAC address for testing cmd := dms3libs.LibConfig.SysCommands["CAT"] + " /sys/class/net/" + curInterface + "/address" - if res, err := exec.Command("bash", "-c", cmd).Output(); err != nil { + + if res, err := exec.Command(dms3libs.LibConfig.SysCommands["BASH"], "-c", cmd).Output(); err != nil { t.Error("Unable to determine local MAC address for testing") } else { localMAC = append(localMAC, string(res)) diff --git a/dms3libs/tests/lib_os_test.go b/dms3libs/tests/lib_os_test.go new file mode 100644 index 0000000..aae356d --- /dev/null +++ b/dms3libs/tests/lib_os_test.go @@ -0,0 +1,43 @@ +package dms3libs_test + +import ( + "testing" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" +) + +func TestDeviceHostname(t *testing.T) { + + val := dms3libs.GetDeviceHostname() + + if val != "" { + t.Log("Success, devicehost is", val) + } else { + t.Error("Failure. Unable to find devicehost") + } + +} + +func TestDeviceOSName(t *testing.T) { + + val := dms3libs.GetDeviceOSName() + + if val != "" { + t.Log("Success, device OS name is", val) + } else { + t.Error("Failure. Unable to find device OS name") + } + +} + +func TestGetDeviceDetails(t *testing.T) { + + val := dms3libs.GetDeviceDetails(dms3libs.Sysname) + + if val != "" { + t.Log("Success, device platform is", val) + } else { + t.Error("Failure. Unable to find device details") + } + +} diff --git a/dms3libs/tests/lib_process_test.go b/dms3libs/tests/lib_process_test.go index fdfadc1..4ba9ff9 100644 --- a/dms3libs/tests/lib_process_test.go +++ b/dms3libs/tests/lib_process_test.go @@ -1,22 +1,24 @@ package dms3libs_test import ( - "go-distributed-motion-s3/dms3libs" + "path/filepath" "testing" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) func init() { - dms3libs.LoadLibConfig("../../config/dms3libs.toml") + dms3libs.LoadLibConfig(filepath.Join("..", "..", "config", "dms3libs.toml")) } func TestRunCommand(t *testing.T) { - testCommand := "ls" + testCommand := dms3libs.LibConfig.SysCommands["ARP"] if res, err := dms3libs.RunCommand(testCommand); err != nil { - t.Error("command " + testCommand + " failed") + t.Error("Command " + testCommand + " failed") } else if len(res) == 0 { - t.Error("output from command " + testCommand + " failed") + t.Error("Output from command " + testCommand + " failed") } } @@ -24,7 +26,7 @@ func TestRunCommand(t *testing.T) { func TestIsRunning(t *testing.T) { // ACTION: set to known active process - testApplication := "gocode" + testApplication := "gopls" if !dms3libs.IsRunning(testApplication) { t.Error(testApplication + " command not running") @@ -32,32 +34,10 @@ func TestIsRunning(t *testing.T) { } -func TestGetPIDCount(t *testing.T) { - - // ACTION: set to known active process - testApplication := "gocode" - - if dms3libs.GetPIDCount(testApplication) < 1 { - t.Error("command failed") - } - -} - -func TestGetPID(t *testing.T) { - - // ACTION: set to known active process - testApplication := "gocode" - - if dms3libs.GetPID(testApplication) == 0 { - t.Error("command failed") - } - -} - func TestStartStopApplication(t *testing.T) { // ACTION: set to known installed application configured to run as service - // NOTE: should be run with root permissions (if running as daemon) + // NOTE: assumes motion program (https://motion-project.github.io/) is installed // testApplication := "motion" diff --git a/dms3libs/tests/lib_util_test.go b/dms3libs/tests/lib_util_test.go index f1eb6f3..c777e77 100644 --- a/dms3libs/tests/lib_util_test.go +++ b/dms3libs/tests/lib_util_test.go @@ -1,34 +1,123 @@ package dms3libs_test import ( - "go-distributed-motion-s3/dms3libs" + "path/filepath" "testing" + "time" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) -func init() { - dms3libs.LoadLibConfig("../../config/dms3libs.toml") -} +func TestGetFunctionName(t *testing.T) { -func TestPrintFuncName(t *testing.T) { + val := filepath.Base(dms3libs.GetFunctionName()) - dms3libs.LogDebug(dms3libs.GetFunctionName()) + if val != "" { + t.Log("Success, function name is", val) + } else { + t.Error("Failure. Unable to get function name") + } } func TestGetPackageDir(t *testing.T) { - dms3libs.GetPackageDir() + val := dms3libs.GetPackageDir() + + if val != "" { + t.Log("Success, package dir is", val) + } else { + t.Error("Failure. Unable to get package dir") + } } func TestStripRet(t *testing.T) { testArray := []byte{50, 40, 30, 20, 10} - res := dms3libs.StripRet(testArray) if len(res) != len(testArray)-1 { - t.Error("command failed") + t.Error("function failed") + } + +} + +func TestUptime(t *testing.T) { + + testTime := time.Now() + time.Sleep(1000 * time.Millisecond) // force uptime value + val := dms3libs.Uptime(testTime) + + if val != "000d:00h:00m:00s" { + t.Log("Success, uptime is", val) + } else { + t.Error("Failure. Unable to get uptime") + } + +} + +func TestSecondsSince(t *testing.T) { + + testTime := time.Now() + time.Sleep(1000 * time.Millisecond) + val := dms3libs.SecondsSince(testTime) + + if val >= 1 { + t.Log("Success, seconds since", testTime, "is", val) + } else { + t.Error("Failure. Unable to get seconds since", testTime) + } + +} + +func TestTo24H(t *testing.T) { + + testTime := time.Now() + val := dms3libs.To24H(testTime) + + if val == testTime.Format("150405") { + t.Log("Success, current time to 24H format is", val) + } else { + t.Error("Failure. Unable to convert current time") + } + +} + +func TestFormatDateTime(t *testing.T) { + + testTime := time.Now() + val := dms3libs.FormatDateTime(testTime) + + if val == testTime.Format("2006-01-02 at 15:04:05") { + t.Log("Success, current time formatted is", val) + } else { + t.Error("Failure. Unable to format time") + } + +} + +func TestModVal(t *testing.T) { + + dividend := 30 + divisor := 10 + val := dms3libs.ModVal(dividend, divisor) + + if val == 0 { + t.Log("Success, ModVal(", dividend, ",", divisor, ") is", val) + } else { + t.Error("Failure. ModVal(", dividend, ",", divisor, ") yielded", val) + } + +} + +func TestGetImageDimensions(t *testing.T) { + + imageFile := filepath.Join(dms3libs.GetPackageDir(), "lib_util_test.jpg") + w, h := dms3libs.GetImageDimensions(imageFile) + + if w != 100 && h != 100 { + t.Error("Failure. Dimensions do not match image") } } diff --git a/dms3libs/tests/lib_util_test.jpg b/dms3libs/tests/lib_util_test.jpg new file mode 100644 index 0000000..019becc Binary files /dev/null and b/dms3libs/tests/lib_util_test.jpg differ diff --git a/dms3mail/assets/img/dms3github.png b/dms3mail/assets/img/dms3github.png new file mode 100644 index 0000000..dcb5eb2 Binary files /dev/null and b/dms3mail/assets/img/dms3github.png differ diff --git a/dms3mail/assets/img/dms3logo.png b/dms3mail/assets/img/dms3logo.png new file mode 100644 index 0000000..637b9d9 Binary files /dev/null and b/dms3mail/assets/img/dms3logo.png differ diff --git a/dms3mail/dms3mail.html b/dms3mail/dms3mail.html new file mode 100644 index 0000000..9a6272a --- /dev/null +++ b/dms3mail/dms3mail.html @@ -0,0 +1,179 @@ + + + + + + + + + + + + + DMS3 Motion Detected + + + + + + + + + + +
+ + + + + + + +
+ + + \ No newline at end of file diff --git a/dms3mail/dms3mail_base.html b/dms3mail/dms3mail_base.html new file mode 100644 index 0000000..2465d45 --- /dev/null +++ b/dms3mail/dms3mail_base.html @@ -0,0 +1,235 @@ + + + + + + + + + + + + + DMS3 Motion Detected + + + + + + + + + + +
+ + + + + + + +
+ + + \ No newline at end of file diff --git a/dms3mail/mail_config.go b/dms3mail/mail_config.go index d4ebf45..ef6af5c 100644 --- a/dms3mail/mail_config.go +++ b/dms3mail/mail_config.go @@ -2,34 +2,48 @@ // package dms3mail -import ( - "go-distributed-motion-s3/dms3libs" -) +import "github.com/richbl/go-distributed-motion-s3/dms3libs" // mailConfig contains dms3mail configuration settings read from TOML file -var mailConfig *structSettings +var mailConfig *structEmailSettings // motion mail configuration parameters -type structSettings struct { - Email *structEmail - SMTP *structSMTP - Logging *dms3libs.StructLogging +type structEmailSettings struct { + Filename string + FileLocation string + Email *structEmail + SMTP *structSMTP + Logging *dms3libs.StructLogging } // email composition parameters type structEmail struct { To string From string - Body string } // SMTP mailer parameters type structSMTP struct { - Address string - Port int - Domain string - Username string - Password string - Authentication string - EnableStartTLSAuto bool + Address string + Port int + Username string + Password string +} + +// event details to be sent via email +type structEventDetails struct { + eventMedia string + eventDate string + eventChange string + clientName string +} + +// HTML tokens used in email template +type emailTemplateElements struct { + Header string + Event string + Date string + Client string + Change string + Footer string } diff --git a/dms3mail/motion_mail.go b/dms3mail/motion_mail.go index fd3fd53..664d0b4 100644 --- a/dms3mail/motion_mail.go +++ b/dms3mail/motion_mail.go @@ -3,77 +3,78 @@ package dms3mail import ( + "bytes" "flag" - "go-distributed-motion-s3/dms3libs" + "fmt" + "html/template" + "math" "path" "path/filepath" "strconv" "strings" "time" + "github.com/richbl/go-distributed-motion-s3/dms3libs" "gopkg.in/gomail.v2" ) -type structEventDetails struct { - eventNumber string - eventMedia string - eventDate string - cameraNumber int - pixelsDetected int -} - // Init configs the library and configuration for dms3mail +// func Init(configPath string) { - dms3libs.LoadLibConfig(filepath.Join(configPath, "dms3libs/dms3libs.toml")) - dms3libs.LoadComponentConfig(&mailConfig, filepath.Join(configPath, "dms3mail/dms3mail.toml")) + dms3libs.LoadLibConfig(filepath.Join(configPath, "dms3libs", "dms3libs.toml")) + dms3libs.LoadComponentConfig(&mailConfig, filepath.Join(configPath, "dms3mail", "dms3mail.toml")) dms3libs.SetLogFileLocation(mailConfig.Logging) dms3libs.CreateLogger(mailConfig.Logging) + dms3libs.LogInfo("dms3mail started") + + dms3libs.CheckFileLocation(configPath, "dms3mail", &mailConfig.FileLocation, mailConfig.Filename) GenerateEventEmail() } -// GenerateEventEmail is the entry point for this package, first calling parseEvent to interpret +// GenerateEventEmail is the entry point for this package, first calling parseEventArgs to interpret // the Motion event, and then calling generateSMTPEmail to create and send the email // func GenerateEventEmail() { var eventDetails structEventDetails - parseEvent(&eventDetails) - generateSMTPEmail(&eventDetails) + eventDetails.parseEventArgs() + eventDetails.generateSMTPEmail() } -// parseEvent creates an event by parsing the following command line arguments passed in via the +// parseEventArgs creates an event by parsing the following command line arguments passed in via the // Motion on_picture_save or the on_movie_end command: // -// ARGV[0] pixels detected +// ARGV[0] count of pixels changed // ARGV[1] media filename -// ARGV[2] device (camera) number // -func parseEvent(eventDetails *structEventDetails) { +func (eventDetails *structEventDetails) parseEventArgs() { + // parse command line arguments passed from Motion command pixels := flag.Int("pixels", 0, "count of pixels detected in the event") filename := flag.String("filename", "", "fullpath filename of the event media file") - camera := flag.Int("camera", 0, "camera number that captured the event") flag.Parse() - if flag.NFlag() != 3 { + if flag.NFlag() != 2 { dms3libs.LogFatal("only " + strconv.Itoa(flag.NFlag()) + " argument(s) passed... exiting") - } - - if !dms3libs.IsFile(*filename) { + } else if !dms3libs.IsFile(*filename) { dms3libs.LogFatal("filename not found... exiting") } - eventDetails.cameraNumber = *camera eventDetails.eventMedia = *filename - eventDetails.pixelsDetected = *pixels - eventDetails.eventNumber, eventDetails.eventDate = getEventDetails(*filename) + + // get image dimensions and calculate percent of image change + width, height := dms3libs.GetImageDimensions(eventDetails.eventMedia) + eventDetails.eventChange = fmt.Sprintf("%d", int(math.Ceil((float64(*pixels) / float64(width*height) * 100)))) + + eventDetails.eventDate = getEventDetails(eventDetails.eventMedia) + eventDetails.clientName = strings.Title(dms3libs.GetDeviceHostname()) } @@ -82,64 +83,85 @@ func parseEvent(eventDetails *structEventDetails) { // eventNumber - Motion-generated event number // eventDate - Motion-generated event datetime // -// NOTE: this method assumes that filename follows the default Motion file-naming convention of -// %v-%Y%m%d%H%M%S (for movies) or %v-%Y%m%d%H%M%S-%q (for pictures), where: +// This method assumes that filename follows the default Motion file-naming convention of +// [%v-]%Y%m%d%H%M%S (for movies) or [%v-]%Y%m%d%H%M%S-%q (for pictures), where: // -// %v - Motion-generated event number +// [%v] - event number (as of Motion 4.3.2, no longer included in filename by default) // %Y%m%d%H%M%S - ISO 8601 date, with hours, minutes, seconds notion // %q - frame number (value ignored) // -func getEventDetails(filename string) (eventNumber string, eventDate string) { +func getEventDetails(filename string) (eventDate string) { + var index int file := path.Base(filename) sepCount := strings.Count(file, "-") - if sepCount != 1 && sepCount != 2 { - dms3libs.LogFatal("bad file-naming convention... exiting") + if sepCount > 0 && sepCount < 3 { + index = sepCount - 1 + } else { + dms3libs.LogFatal("unexpected Motion filenaming convention: missing separators") } res := strings.Split(file, "-") - year, _ := strconv.Atoi(res[1][0:4]) - month, _ := strconv.Atoi(res[1][4:6]) - day, _ := strconv.Atoi(res[1][6:8]) - hour, _ := strconv.Atoi(res[1][8:10]) - min, _ := strconv.Atoi(res[1][10:12]) - sec, _ := strconv.Atoi(res[1][12:14]) - return res[0], time.Date(year, time.Month(month), day, hour, min, sec, 0, time.UTC).Format("2006-01-02 at 15:04:05") + if len(res[index]) != 14 { + dms3libs.LogFatal("unexpected Motion filenaming convention: incorrect string length") + } + + year, _ := strconv.Atoi(res[index][0:4]) + month, _ := strconv.Atoi(res[index][4:6]) + day, _ := strconv.Atoi(res[index][6:8]) + hour, _ := strconv.Atoi(res[index][8:10]) + min, _ := strconv.Atoi(res[index][10:12]) + sec, _ := strconv.Atoi(res[index][12:14]) + + return time.Date(year, time.Month(month), day, hour, min, sec, 0, time.UTC).Format("15:04:05 on 2006-01-02") } -// createEmailBody performs a placeholder replacement in the email body with eventDetails elements -func createEmailBody(eventDetails *structEventDetails) string { +// createEmailBody loads the email template (HTML) and parses elements +// +func (elements emailTemplateElements) createEmailBody() string { - var replacements = map[string]string{ - "!EVENT": eventDetails.eventNumber, - "!PIXELS": strconv.Itoa(eventDetails.pixelsDetected), - "!CAMERA": strconv.Itoa(eventDetails.cameraNumber), - } + t := template.Must(template.New(mailConfig.Filename).ParseFiles(filepath.Join(mailConfig.FileLocation, mailConfig.Filename))) - processedEmailBody := mailConfig.Email.Body + var tpl bytes.Buffer - for key, val := range replacements { - processedEmailBody = strings.Replace(processedEmailBody, key, val, -1) + if err := t.Execute(&tpl, elements); err != nil { + dms3libs.LogFatal(err.Error()) } - return processedEmailBody + return tpl.String() } // generateSMTPEmail generates and mails an email message based on configuration options // See https://github.com/go-gomail/gomail for mail package options // -func generateSMTPEmail(eventDetails *structEventDetails) { +func (eventDetails *structEventDetails) generateSMTPEmail() { mail := gomail.NewMessage() mail.SetHeader("From", mailConfig.Email.From) mail.SetHeader("To", mailConfig.Email.To) - mail.SetHeader("Subject", "Motion Detected on Camera #"+strconv.Itoa(eventDetails.cameraNumber)+" at "+eventDetails.eventDate) - mail.SetBody("text/html", createEmailBody(eventDetails)) - mail.Attach(eventDetails.eventMedia) + mail.SetHeader("Subject", "Motion Detected on Device Client "+eventDetails.clientName+" at "+eventDetails.eventDate) + + headerImage := filepath.Join(mailConfig.FileLocation, "assets", "img", "dms3logo.png") + footerImage := filepath.Join(mailConfig.FileLocation, "assets", "img", "dms3github.png") + + elements := &emailTemplateElements{ + Header: filepath.Base(headerImage), + Event: filepath.Base(eventDetails.eventMedia), + Date: eventDetails.eventDate, + Client: eventDetails.clientName, + Change: eventDetails.eventChange, + Footer: filepath.Base(footerImage), + } + + mail.SetBody("text/html", elements.createEmailBody()) + + mail.Embed(headerImage) + mail.Embed(eventDetails.eventMedia) + mail.Embed(footerImage) dialer := gomail.NewDialer(mailConfig.SMTP.Address, mailConfig.SMTP.Port, mailConfig.SMTP.Username, mailConfig.SMTP.Password) diff --git a/dms3server/daemons/systemd/dms3server.service b/dms3server/daemons/systemd/dms3server.service index f2ec731..41e9ae6 100644 --- a/dms3server/daemons/systemd/dms3server.service +++ b/dms3server/daemons/systemd/dms3server.service @@ -5,7 +5,7 @@ After=network.target [Service] Type=simple Restart=on-failure -ExecStart=/usr/local/bin/go_dms3server +ExecStart=/usr/local/bin/dms3server [Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/dms3server/server_config.go b/dms3server/server_config.go index 2d40dfc..71c3be4 100644 --- a/dms3server/server_config.go +++ b/dms3server/server_config.go @@ -3,17 +3,11 @@ package dms3server import ( - "go-distributed-motion-s3/dms3libs" - "log" - "path" - "path/filepath" - "time" + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) -var startTime time.Time - -// serverConfig contains dms3Server configuration settings read from TOML file -var serverConfig *structSettings +// ServerConfig contains dms3Server configuration settings read from TOML file +var ServerConfig *structSettings // server-side configuration parameters type structSettings struct { @@ -26,8 +20,9 @@ type structSettings struct { // server details type structServer struct { - Port int - CheckInterval int + Port int + CheckInterval int + EnableDashboard bool } // audio parameters used when the motion detector application starts/stops @@ -49,58 +44,3 @@ type structUserProxy struct { IPRange []int MacsToFind []string } - -// setMediaLocation sets the location where audio files are located for motion detection -// application start/stop events -// -func setMediaLocation(configPath string, config *structSettings) { - - type mediaPath struct { - configLocation *string - mediaLocation string - } - - media := []mediaPath{ - { - &config.Audio.PlayMotionStart, - "/dms3server/media/motion_start.wav", - }, - { - &config.Audio.PlayMotionStop, - "/dms3server/media/motion_stop.wav", - }, - } - - fail := false - - for i := range media { - - relPath := filepath.Join(configPath, media[i].mediaLocation) - devPath := filepath.Join(path.Dir(dms3libs.GetPackageDir()), media[i].mediaLocation) - - if !dms3libs.IsFile(*media[i].configLocation) { - - // if no location set, set to release folder, else set to development folder - if *media[i].configLocation == "" { - - if dms3libs.IsFile(relPath) { - *media[i].configLocation = relPath - } else if dms3libs.IsFile(devPath) { - *media[i].configLocation = devPath - } else { - fail = true - } - - } else { - fail = true - } - - if fail { - log.Fatalln("unable to set media location... check TOML configuration file") - } - - } - - } - -} diff --git a/dms3server/server_connector.go b/dms3server/server_connector.go index d07857b..343f304 100644 --- a/dms3server/server_connector.go +++ b/dms3server/server_connector.go @@ -4,54 +4,48 @@ package dms3server import ( "fmt" - "go-distributed-motion-s3/dms3dashboard" - "go-distributed-motion-s3/dms3libs" "net" + "path" "path/filepath" "strconv" + + dms3dash "github.com/richbl/go-distributed-motion-s3/dms3dashboard" + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) // Init configs the library and configuration for dms3server +// func Init(configPath string) { - dms3libs.SetUptime(&startTime) - - dms3libs.LoadLibConfig(filepath.Join(configPath, "dms3libs/dms3libs.toml")) - dms3libs.LoadComponentConfig(&serverConfig, filepath.Join(configPath, "dms3server/dms3server.toml")) + dms3libs.LogDebug(filepath.Base((dms3libs.GetFunctionName()))) - dms3libs.SetLogFileLocation(serverConfig.Logging) - dms3libs.CreateLogger(serverConfig.Logging) + dms3libs.LoadLibConfig(filepath.Join(configPath, "dms3libs", "dms3libs.toml")) + dms3libs.LoadComponentConfig(&ServerConfig, filepath.Join(configPath, "dms3server", "dms3server.toml")) - setMediaLocation(configPath, serverConfig) + dms3libs.SetLogFileLocation(ServerConfig.Logging) + dms3libs.CreateLogger(ServerConfig.Logging) - dms3dash.InitDashboardServer(configPath, configDashboardServerMetrics()) - startServer(serverConfig.Server.Port) - -} + dms3libs.LogInfo("dms3server started") -// configDashboardServerMetrics initializes the DeviceMetrics struct used by dms3dashboard -func configDashboardServerMetrics() *dms3dash.DeviceMetrics { + setMediaLocation(configPath, ServerConfig) + dms3dash.DashboardEnable = ServerConfig.Server.EnableDashboard - dm := &dms3dash.DeviceMetrics{ - Platform: dms3dash.DevicePlatform{ - Type: dms3dash.Client, - }, - Period: dms3dash.DeviceTime{ - StartTime: startTime, - CheckInterval: serverConfig.Server.CheckInterval, - }, + if dms3dash.DashboardEnable { + dms3dash.InitDashboardServer(configPath, ServerConfig.Server.CheckInterval) } - return dm + startServer(ServerConfig.Server.Port) } // startServer starts the TCP server +// func startServer(serverPort int) { if listener, error := net.Listen("tcp", ":"+fmt.Sprint(serverPort)); error != nil { dms3libs.LogFatal(error.Error()) } else { + dms3libs.LogInfo("TCP server started") defer listener.Close() serverLoop(listener) } @@ -77,9 +71,10 @@ func serverLoop(listener net.Listener) { } // processClient passes motion detector application state to all dms3client listeners +// func processClient(conn net.Conn) { - dms3libs.LogDebug(dms3libs.GetFunctionName()) + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) dms3dash.SendDashboardRequest(conn) sendMotionDetectorState(conn) @@ -90,6 +85,7 @@ func processClient(conn net.Conn) { } // sendMotionDetectorState sends detector state to clients +// func sendMotionDetectorState(conn net.Conn) { state := strconv.Itoa(int(DetermineMotionDetectorState())) @@ -101,3 +97,59 @@ func sendMotionDetectorState(conn net.Conn) { } } + +// setMediaLocation sets the location where audio files are located for motion detection +// application start/stop events +// +func setMediaLocation(configPath string, config *structSettings) { + + type mediaPath struct { + configLocation *string + mediaLocation string + } + + media := []mediaPath{ + { + &config.Audio.PlayMotionStart, + filepath.Join(string(filepath.Separator), "dms3server", "media", "motion_start.wav"), + }, + { + &config.Audio.PlayMotionStop, + + filepath.Join(string(filepath.Separator), "dms3server", "media", "motion_stop.wav"), + }, + } + + fail := false + + for i := range media { + + relPath := filepath.Join(configPath, media[i].mediaLocation) + devPath := filepath.Join(path.Dir(dms3libs.GetPackageDir()), media[i].mediaLocation) + + if !dms3libs.IsFile(*media[i].configLocation) { + + // if no location set, set to release folder, else set to development folder + if *media[i].configLocation == "" { + + if dms3libs.IsFile(relPath) { + *media[i].configLocation = relPath + } else if dms3libs.IsFile(devPath) { + *media[i].configLocation = devPath + } else { + fail = true + } + + } else { + fail = true + } + + if fail { + dms3libs.LogFatal("unable to set media location... check TOML configuration file") + } + + } + + } + +} diff --git a/dms3server/server_manager.go b/dms3server/server_manager.go index 88089a5..6a29aa3 100644 --- a/dms3server/server_manager.go +++ b/dms3server/server_manager.go @@ -4,8 +4,10 @@ package dms3server import ( - "go-distributed-motion-s3/dms3libs" + "path/filepath" "time" + + "github.com/richbl/go-distributed-motion-s3/dms3libs" ) var checkIntervalTimestamp = time.Now() @@ -15,7 +17,7 @@ var checkIntervalTimestamp = time.Now() // func DetermineMotionDetectorState() dms3libs.MotionDetectorState { - dms3libs.LogDebug(dms3libs.GetFunctionName()) + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) if checkIntervalExpired() { @@ -35,7 +37,7 @@ func DetermineMotionDetectorState() dms3libs.MotionDetectorState { // func setMotionDetectorState(state dms3libs.MotionDetectorState) dms3libs.MotionDetectorState { - dms3libs.LogDebug(dms3libs.GetFunctionName()) + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) if dms3libs.MotionDetector.State() == state { return state @@ -43,13 +45,13 @@ func setMotionDetectorState(state dms3libs.MotionDetectorState) dms3libs.MotionD dms3libs.MotionDetector.SetState(state) - if serverConfig.Audio.Enable { + if ServerConfig.Audio.Enable { switch state { case dms3libs.Start: - dms3libs.PlayAudio(serverConfig.Audio.PlayMotionStart) + dms3libs.PlayAudio(ServerConfig.Audio.PlayMotionStart) case dms3libs.Stop: - dms3libs.PlayAudio(serverConfig.Audio.PlayMotionStop) + dms3libs.PlayAudio(ServerConfig.Audio.PlayMotionStop) } } @@ -59,11 +61,12 @@ func setMotionDetectorState(state dms3libs.MotionDetectorState) dms3libs.MotionD } // checkIntervalExpired determines if last check interval (in seconds) has expired +// func checkIntervalExpired() bool { - dms3libs.LogDebug(dms3libs.GetFunctionName()) + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) - if time.Since(checkIntervalTimestamp).Seconds() >= float64(serverConfig.Server.CheckInterval) { + if time.Since(checkIntervalTimestamp).Seconds() >= float64(ServerConfig.Server.CheckInterval) { checkIntervalTimestamp = time.Now() return true } @@ -77,9 +80,9 @@ func checkIntervalExpired() bool { // func timeInRange() bool { - dms3libs.LogDebug(dms3libs.GetFunctionName()) + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) - if serverConfig.AlwaysOn.Enable { + if ServerConfig.AlwaysOn.Enable { return calcTimeRange() } @@ -92,12 +95,12 @@ func timeInRange() bool { // func calcTimeRange() bool { - dms3libs.LogDebug(dms3libs.GetFunctionName()) + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) curTime := dms3libs.To24H(time.Now()) - startTime := dms3libs.Format24H(serverConfig.AlwaysOn.TimeRange[0]) - endTime := dms3libs.Format24H(serverConfig.AlwaysOn.TimeRange[1]) + startTime := dms3libs.Format24H(ServerConfig.AlwaysOn.TimeRange[0]) + endTime := dms3libs.Format24H(ServerConfig.AlwaysOn.TimeRange[1]) if startTime > endTime { return (curTime >= startTime) || (curTime < endTime) @@ -112,8 +115,8 @@ func calcTimeRange() bool { // func deviceOnLAN() bool { - dms3libs.LogDebug(dms3libs.GetFunctionName()) - dms3libs.PingHosts(serverConfig.UserProxy.IPBase, serverConfig.UserProxy.IPRange) - return dms3libs.FindMacs(serverConfig.UserProxy.MacsToFind) + dms3libs.LogDebug(filepath.Base(dms3libs.GetFunctionName())) + dms3libs.PingHosts(ServerConfig.UserProxy.IPBase, ServerConfig.UserProxy.IPRange) + return dms3libs.FindMacs(ServerConfig.UserProxy.MacsToFind) } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1cf7afa --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/richbl/go-distributed-motion-s3 + +go 1.17 + +require ( + github.com/BurntSushi/toml v0.4.1 + github.com/mrgleam/easyssh v0.0.0-20170611150909-933e069a250a + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df +) + +require ( + golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect + golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7c43ca3 --- /dev/null +++ b/go.sum @@ -0,0 +1,20 @@ +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/mrgleam/easyssh v0.0.0-20170611150909-933e069a250a h1:xB0uQ78Pxb3UQ+A9cG0RsdyBxINcRfKeexlf5AHp4Dc= +github.com/mrgleam/easyssh v0.0.0-20170611150909-933e069a250a/go.mod h1:PEFDTl2WSHEVdE4ERbP0mu74cH7SBB/D5BFc09pOYcM= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= +golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= diff --git a/go_dms3client.go b/go_dms3client.go deleted file mode 100644 index 7d328d4..0000000 --- a/go_dms3client.go +++ /dev/null @@ -1,11 +0,0 @@ -// Package main go_dms3client initializes a dms3client device component -// -package main - -import ( - "go-distributed-motion-s3/dms3client" -) - -func main() { - dms3client.Init("/etc/distributed-motion-s3") -} diff --git a/go_dms3mail.go b/go_dms3mail.go deleted file mode 100644 index 32c90fd..0000000 --- a/go_dms3mail.go +++ /dev/null @@ -1,11 +0,0 @@ -// Package main go_dms3mail initializes a dms3mail device component -// -package main - -import ( - "go-distributed-motion-s3/dms3mail" -) - -func main() { - dms3mail.Init("/etc/distributed-motion-s3") -} diff --git a/go_dms3server.go b/go_dms3server.go deleted file mode 100644 index 9439c05..0000000 --- a/go_dms3server.go +++ /dev/null @@ -1,11 +0,0 @@ -// Package main go_dms3server initializes a dms3server device component -// -package main - -import ( - "go-distributed-motion-s3/dms3server" -) - -func main() { - dms3server.Init("/etc/distributed-motion-s3") -} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy