mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2024-12-26 23:25:55 -05:00
Merge branch 'master' (early part) into new-schema
Catch the new-schema branch up with everything up to (but not including) the big UI refactoring. I'll merge that separately.
This commit is contained in:
commit
299c0b1802
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,8 +1,11 @@
|
|||||||
cameras.sql
|
cameras.sql
|
||||||
|
.DS_Store
|
||||||
.project
|
.project
|
||||||
.settings
|
.settings
|
||||||
*.swp
|
*.swp
|
||||||
node_modules
|
node_modules
|
||||||
prep.config
|
prep.config
|
||||||
|
settings-nvr-local.js
|
||||||
target
|
target
|
||||||
ui-dist
|
ui-dist
|
||||||
|
yarn-error.log
|
||||||
|
@ -56,6 +56,7 @@ make this possible:
|
|||||||
|
|
||||||
* [License](LICENSE.txt) — GPLv3
|
* [License](LICENSE.txt) — GPLv3
|
||||||
* [Building and installing](guide/install.md)
|
* [Building and installing](guide/install.md)
|
||||||
|
* [UI Development](guide/developing-ui.md)
|
||||||
* [Troubleshooting](guide/troubleshooting.md)
|
* [Troubleshooting](guide/troubleshooting.md)
|
||||||
|
|
||||||
# <a name="help"></a> Getting help and getting involved
|
# <a name="help"></a> Getting help and getting involved
|
||||||
|
146
guide/developing-ui.md
Normal file
146
guide/developing-ui.md
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# Working on UI development
|
||||||
|
|
||||||
|
The UI is presented from a single HTML page (index.html) and any number
|
||||||
|
of Javascript files, css files, images, etc. These are "packed" together
|
||||||
|
using [webpack](https://webpack.js.org).
|
||||||
|
|
||||||
|
For ongoing development it is possible to have the UI running in a web
|
||||||
|
browser using "hot loading". This means that as you make changes to source
|
||||||
|
files, they will be detected, the webpack will be recompiled and generated
|
||||||
|
and then the browser will be informed to reload things. In combination with
|
||||||
|
the debugger built into modern browsers this makes for a reasonable process.
|
||||||
|
|
||||||
|
For a production build, the same process is followed, except with different
|
||||||
|
settings. In particular, no hot loading development server will be started
|
||||||
|
and more effort is expended on packing and minimizing the components of
|
||||||
|
the application as represented in the various "bundles". Read more about
|
||||||
|
this in the webpack documentation.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
Checkout the branch you want to work on and type
|
||||||
|
|
||||||
|
$ yarn start
|
||||||
|
|
||||||
|
This will pack and prepare a development setup. By default the development
|
||||||
|
server that serves up the web page(s) will listen on
|
||||||
|
[http://localhost:3000](http://localhost:3000) so you can direct your browser
|
||||||
|
there.
|
||||||
|
|
||||||
|
Make any changes to the source code as you desire (look at existing code
|
||||||
|
for examples and typical style), and the browser will hot-load your changes.
|
||||||
|
Often times you will make mistakes. Anything from a coding error (for which
|
||||||
|
you can use the browser's debugger), or compilation breaking Javascript errors.
|
||||||
|
The latter will often be reported as errors during the webpack assembly
|
||||||
|
process, but some will show up in the browser console, or both.
|
||||||
|
|
||||||
|
## Control and location of settings
|
||||||
|
|
||||||
|
Much of the settings needed to put the UI together, run webpack etc. is
|
||||||
|
located in a series of files that contain webpack configuration. These
|
||||||
|
files all live in the "webpack" subdirectory. We will not explain all
|
||||||
|
of them here, as you should rarely need to understand them, let alone
|
||||||
|
modify them.
|
||||||
|
|
||||||
|
What is worth mentioning is that the `package.json` file is configured
|
||||||
|
to use a different webpack configuration for development vs. production
|
||||||
|
builds. Both configurations depend on a shared configuration common to
|
||||||
|
both.
|
||||||
|
|
||||||
|
There are also some settings that control aspects of the MoonFire UI
|
||||||
|
behavior, such as window titles etc. These settings are found in the
|
||||||
|
`settings-nvr.js` file in the project root directory. They should be
|
||||||
|
pretty self explanatory.
|
||||||
|
|
||||||
|
The webpack configuration for all webpack builds is able to load the
|
||||||
|
values from `settings-nvr.js` and then, if the file exists, load those
|
||||||
|
from `settings-nvr-local.js` and use them to add to the configuration,
|
||||||
|
or replace. You can take advantage of this to add your own configuration
|
||||||
|
values in the "local" file, which does not get checked in, but is used
|
||||||
|
to affect development, and production builds.
|
||||||
|
|
||||||
|
## Special considerations for API calls
|
||||||
|
|
||||||
|
The UI code will make calls to the MoonFire NVR's server API, which is
|
||||||
|
assumed to run on the same host as the MoonFire server. This makes sense
|
||||||
|
because that server is also normally the one serving up the UI. For UI
|
||||||
|
development, however this is not always convenient, or even useful.
|
||||||
|
|
||||||
|
For one, you may be doing this development on a machine other than where
|
||||||
|
the main MoonFire server is running. That can work, but of course that
|
||||||
|
machine will not be responding the the API calls. If the UI does not
|
||||||
|
gracefully handle API failure errors (it should but development on that
|
||||||
|
is ongoing), it may break your UI code.
|
||||||
|
|
||||||
|
Therefore, for practical purposes, you may want the API calls to go to
|
||||||
|
a different server than where the localhost is. Rather than editing the
|
||||||
|
`webpack/dev.conf.js` file to make this happen, you should use a different
|
||||||
|
mechanism. The reason for not modifying this file (unless the change is
|
||||||
|
needed by all), is that the file is under source control and thus should
|
||||||
|
not reflect settings that are just for your personal use.
|
||||||
|
|
||||||
|
The manner in which you can achieve using a different server for API
|
||||||
|
calls is by telling the development server to use a "proxy" for
|
||||||
|
certain urls. All API calls start with "/api", so we'll take advantage
|
||||||
|
of that. Create a the file `settings-nvr-local.js` right next to the standard
|
||||||
|
`settings-nvr.js`. The file should look something like this:
|
||||||
|
|
||||||
|
module.exports.settings = {
|
||||||
|
devServer: {
|
||||||
|
proxy: {
|
||||||
|
'/api': 'http://192.168.1.232:8080'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
This tells the development server to proxy all urls that it encounters
|
||||||
|
requests for to the url specified. The way this is done is by taking the
|
||||||
|
full URI component for the original request, and appending it to the string
|
||||||
|
configured on the right side of the path to be rewritten above. In a standard
|
||||||
|
MoonFire install, the server (and thus the API server as well), will be
|
||||||
|
listening on port 8080 a the IP address where the server lives. So adjust
|
||||||
|
the above for reach your own MoonFire instance where there is a server
|
||||||
|
running with some real data behind it.
|
||||||
|
|
||||||
|
### Issues with "older" MoonFire builds
|
||||||
|
|
||||||
|
You might also have though to change the "/api" string in the source code
|
||||||
|
to include the IP address of the MoonFire server. This would use the
|
||||||
|
"right" (desirable) URLs, but the requests will fail due to a violation
|
||||||
|
of the Cross-Origin Resource Sharing (CORS) protocol. If you really
|
||||||
|
need to, you can add a configuration option to the MoonFire server
|
||||||
|
by modifying its "service definition". We will not explain here how.
|
||||||
|
|
||||||
|
## Changing development server configuration
|
||||||
|
|
||||||
|
You can find our standard configuration for the development server inside
|
||||||
|
the `webpacks/dev.conf.js` file. Using the technique outlined above you
|
||||||
|
can change ports, ip addresses etc. One example where this may come in
|
||||||
|
useful is that you may want to "test" your new API code, running on
|
||||||
|
machine "A" (from a development server), proxying API requests to machine
|
||||||
|
"B" (because it has real data), from a browser running on machine "C".
|
||||||
|
|
||||||
|
The development server normally is configured to listing on port 3000
|
||||||
|
on "localhost." (which would correspond to machine "A" in this example).
|
||||||
|
However, you cannot use "localhost" on another machine to refer to "A".
|
||||||
|
You may think that using the IP address of "A" works, but it doesn't
|
||||||
|
because "localhost" lives at an IP address local to machine "A".
|
||||||
|
|
||||||
|
To make this work you must tell the development server on host "A" to
|
||||||
|
listen differently. You need to configure it to listen on IP address
|
||||||
|
"0.0.0.0", which means "all available interfaces". Once that is in
|
||||||
|
place you can use the IP address to reach "A" from "C". "A" will then
|
||||||
|
send API requests on to "B", and present final UI using information
|
||||||
|
from "A" and "B" to the browser on "C".
|
||||||
|
|
||||||
|
Modify the local settings to something like this:
|
||||||
|
|
||||||
|
module.exports.settings = {
|
||||||
|
devServer: {
|
||||||
|
host: "0.0.0.0",
|
||||||
|
proxy: {
|
||||||
|
'/api': 'http://192.168.1.232:8080'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
145
guide/easy-install.md
Normal file
145
guide/easy-install.md
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# Installing Moonfire NVR using provided scripts
|
||||||
|
|
||||||
|
There are no binary packages of Moonfire NVR available yet, so it must be built
|
||||||
|
from source. This is made easy using a few scripts that will do the job for you
|
||||||
|
unless you have very a different operating system. The scripts are written and
|
||||||
|
tested under ubuntu and raspbian but should not be hard to modify if necessary.
|
||||||
|
You'll start by downloading Moonfire if you have not already done so.
|
||||||
|
|
||||||
|
## Downloading
|
||||||
|
|
||||||
|
See the [github page](https://github.com/scottlamb/moonfire-nvr) (in case
|
||||||
|
you're not reading this text there already). You can download the bleeding
|
||||||
|
edge version from the command line via git:
|
||||||
|
|
||||||
|
$ git clone https://github.com/scottlamb/moonfire-nvr.git
|
||||||
|
|
||||||
|
## Preparation steps for easy install
|
||||||
|
|
||||||
|
There are a few things to prepare if you want a truly turnkey install, but
|
||||||
|
they are both optional.
|
||||||
|
|
||||||
|
### Dedicated media directory
|
||||||
|
|
||||||
|
An optional, but strongly suggested, step is to setup a dedicated hard disk
|
||||||
|
for recording video.
|
||||||
|
Moonfire works best if the video samples are collected on a hard drive of
|
||||||
|
sufficient capacity and separate from the root and main file systems. This
|
||||||
|
is particularly important on Raspberry Pi based systems as the flash based
|
||||||
|
main file systems have a limited lifetime and are way too small to hold
|
||||||
|
any significant amount of video.
|
||||||
|
If a dedicated hard drive is available, set up the mount point (in this
|
||||||
|
example we'll use /media/nvr/samples):
|
||||||
|
|
||||||
|
$ sudo vim /etc/fstab
|
||||||
|
$ sudo mount /mount/media/samples
|
||||||
|
|
||||||
|
In the fstab you would add a line similar to this:
|
||||||
|
|
||||||
|
/dev/disk/by-uuid/23d550bc-0e38-4825-acac-1cac8a7e091f /media/nvr ext4 defaults,noatime 0 1
|
||||||
|
|
||||||
|
You'll have to lookup the correct uuid for your disk. One way to do that is
|
||||||
|
to issue the following commands:
|
||||||
|
|
||||||
|
$ ls -l /dev/disk/by-uuid
|
||||||
|
|
||||||
|
Locate the device where your disk will be mounted (or is mounted), for example
|
||||||
|
`/dev/sda1`. Now lookup the filename linked to that from the output of the
|
||||||
|
`ls` command. This is the uuid you need.
|
||||||
|
|
||||||
|
The setup script (see below) will create the necessary sample file dir on the mounted
|
||||||
|
hard disk.
|
||||||
|
|
||||||
|
|
||||||
|
## Setting everything up
|
||||||
|
|
||||||
|
Start by executing the setup script:
|
||||||
|
|
||||||
|
$ cd moonfire-nvr
|
||||||
|
$ scripts/setup-ubuntu.sh
|
||||||
|
|
||||||
|
If this is the very first time you run this script, a file named `prep.config`
|
||||||
|
will be created and the script will stop. This file is where you will set
|
||||||
|
or change variables that describe the Moonfire installation you want. The
|
||||||
|
initial execution will put default values in this value, but only for the
|
||||||
|
most commonly changed variables. For a full list of variables, see below.
|
||||||
|
|
||||||
|
Once you modify this file (if desired), you run the setup script again. This
|
||||||
|
time it will use the values in the file and proceed with the setup.
|
||||||
|
The script will download and install any pre-requisites. Watch carefully for
|
||||||
|
error messages. It may be you have conflicting installations. If that is the
|
||||||
|
case you must either resolve those first, or go the manual route.
|
||||||
|
|
||||||
|
The script may be given the "-f" option. If you do, you are telling the script
|
||||||
|
that you do not want any existing installation of ffmpeg to be overwritten with
|
||||||
|
a newer one. This could be important to you. If you do use it, and the version
|
||||||
|
you have installed is not compatible with Moonfire, you will be told through
|
||||||
|
a message. If you have no ffmpeg installed, the option is effectively ignored
|
||||||
|
and the necessary version of ffmpeg will be installed.
|
||||||
|
|
||||||
|
The setup script should only need to be run once (after `prep.config` has been
|
||||||
|
created), although if you do a re-install of Moonfire, in particular a much
|
||||||
|
newer version, it is a good idea to run it again as requirements and pre-requisites
|
||||||
|
may have changed. Running the script multiple times should not have any negative effects.
|
||||||
|
|
||||||
|
*WARNING* It is quite possible that during the running of the setup script,
|
||||||
|
in particular during the building of libavutil you will see several compiler
|
||||||
|
warnings. This, while undesirable, is a direct result of the original
|
||||||
|
developers not cleaning up the cause(s) of these warnings. They are, however,
|
||||||
|
just warnings and will not affect correct functioning of Moonfire.
|
||||||
|
|
||||||
|
Once the setup is complete, two steps remain: building and then installing.
|
||||||
|
There is a script for each of these scenarios, but since generally you would
|
||||||
|
want to install after a succesul build, the build script automatically invokes
|
||||||
|
the install script, unless specifically told not to.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
The build script is involved like this:
|
||||||
|
|
||||||
|
$ scripts/build.sh
|
||||||
|
|
||||||
|
This script will perform all steps necessary to build a complete Moonfire
|
||||||
|
setup. If there are no build errors, this script will then automatically
|
||||||
|
invoke the install script (see below).
|
||||||
|
|
||||||
|
There are two options you may pass to this script. The first is "-B" which
|
||||||
|
means "build only". In other words, this will stop the automatic invocation
|
||||||
|
of the install script. The other option available is "-t" and causes the
|
||||||
|
script to ignore the results of any tests. In other words, even if tests
|
||||||
|
fail, the build phase will be considered successful. This can occasionally
|
||||||
|
be useful if you are doing development, and have temporarily broken one
|
||||||
|
or more test, but want to proceed anyway.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
The install step is performed by the script that can be manually invoked
|
||||||
|
like this:
|
||||||
|
|
||||||
|
$ scripts/install.sh
|
||||||
|
|
||||||
|
This script will copy various files resulting from the build to the correct
|
||||||
|
locations. It will also create a "service configuration" for systemctl that
|
||||||
|
can be used to control Moonfire. This service configuration can be prevented
|
||||||
|
by using the "-s" option to this script. It will also prevent the automatic
|
||||||
|
start of this configuration.
|
||||||
|
|
||||||
|
|
||||||
|
## Configuration variables
|
||||||
|
|
||||||
|
Although not all listed in the default prep.config file, these are the
|
||||||
|
available configuration variable and their defaults. In the most frequent
|
||||||
|
scenarios you will probably only change SAMPLE_MEDIA_DIR to point
|
||||||
|
to your mounted external disk (/media/nvr in the example above).
|
||||||
|
|
||||||
|
NVR_USER=moonfire-nvr
|
||||||
|
NVR_GROUP=$NVR_USER
|
||||||
|
NVR_PORT=8080
|
||||||
|
NVR_HOME_BASE=/var/lib
|
||||||
|
DB_NAME=db
|
||||||
|
DB_DIR=$NVR_HOME/$DB_NAME
|
||||||
|
SAMPLE_FILE_DIR=sample
|
||||||
|
SAMPLE_MEDIA_DIR=$NVR_HOME
|
||||||
|
SERVICE_NAME=moonfire-nvr
|
||||||
|
SERVICE_DESC="Moonfire NVR"
|
||||||
|
SERVICE_BIN=/usr/local/bin/$SERVICE_NAME
|
201
guide/install-manual.md
Normal file
201
guide/install-manual.md
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
# Installing Moonfire NVR
|
||||||
|
|
||||||
|
This document describes how to install Moonfire NVR on a Linux system.
|
||||||
|
|
||||||
|
## Downloading
|
||||||
|
|
||||||
|
See the [github page](https://github.com/scottlamb/moonfire-nvr) (in case
|
||||||
|
you're not reading this text there already). You can download the bleeding
|
||||||
|
edge version from the command line via git:
|
||||||
|
|
||||||
|
$ git clone https://github.com/scottlamb/moonfire-nvr.git
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
|
||||||
|
There are no binary packages of Moonfire NVR available yet, so it must be built
|
||||||
|
from source.
|
||||||
|
|
||||||
|
Moonfire NVR is written in the [Rust Programming
|
||||||
|
Language](https://www.rust-lang.org/en-US/). In the long term, I expect this
|
||||||
|
will result in a more secure, full-featured, easy-to-install software.
|
||||||
|
|
||||||
|
You will need the following C libraries installed:
|
||||||
|
|
||||||
|
* [ffmpeg](http://ffmpeg.org/) version 2.x or 3.x, including `libavutil`,
|
||||||
|
`libavcodec` (to inspect H.264 frames), and `libavformat` (to connect to RTSP
|
||||||
|
servers and write `.mp4` files).
|
||||||
|
|
||||||
|
Note ffmpeg library versions older than 55.1.101, along with all versions of
|
||||||
|
the competing project [libav](http://libav.org), don't support socket
|
||||||
|
timeouts for RTSP. For reliable reconnections on error, it's strongly
|
||||||
|
recommended to use ffmpeg library versions >= 55.1.101.
|
||||||
|
|
||||||
|
* [SQLite3](https://www.sqlite.org/).
|
||||||
|
|
||||||
|
* [`ncursesw`](https://www.gnu.org/software/ncurses/), the UTF-8 version of
|
||||||
|
the `ncurses` library.
|
||||||
|
|
||||||
|
On recent Ubuntu or Raspbian, the following command will install
|
||||||
|
all non-Rust dependencies:
|
||||||
|
|
||||||
|
$ sudo apt-get install \
|
||||||
|
build-essential \
|
||||||
|
libavcodec-dev \
|
||||||
|
libavformat-dev \
|
||||||
|
libavutil-dev \
|
||||||
|
libncurses5-dev \
|
||||||
|
libncursesw5-dev \
|
||||||
|
libsqlite3-dev \
|
||||||
|
libssl-dev \
|
||||||
|
pkgconf
|
||||||
|
|
||||||
|
Next, you need Rust 1.17+ and Cargo. The easiest way to install them is by following
|
||||||
|
the instructions at [rustup.rs](https://www.rustup.rs/).
|
||||||
|
|
||||||
|
Finally, building the UI requires [yarn](https://yarnpkg.com/en/).
|
||||||
|
|
||||||
|
You can continue to follow the build/install instructions below for a manual
|
||||||
|
build and install, or alternatively you can run the prep script called `prep.sh`.
|
||||||
|
|
||||||
|
$ cd moonfire-nvr
|
||||||
|
$ ./prep.sh
|
||||||
|
|
||||||
|
The script will take the following command line options, should you need them:
|
||||||
|
|
||||||
|
* `-S`: Skip updating and installing dependencies through apt-get. This too can be
|
||||||
|
useful on repeated builds.
|
||||||
|
|
||||||
|
You can edit variables at the start of the script to influence names and
|
||||||
|
directories, but defaults should suffice in most cases. For details refer to
|
||||||
|
the script itself. We will mention just one option, needed when you follow the
|
||||||
|
suggestion to separate database and samples between flash storage and a hard disk.
|
||||||
|
If you have the hard disk mounted on, lets say `/media/nvr`, and you want to
|
||||||
|
store the video samples inside a directory named `samples` there, you would set:
|
||||||
|
|
||||||
|
SAMPLE_FILE_DIR=/media/nvr/samples
|
||||||
|
|
||||||
|
The script will perform all necessary steps to leave you with a fully built,
|
||||||
|
installed moonfire-nvr binary. The only thing
|
||||||
|
you'll have to do manually is add your camera configuration(s) to the database.
|
||||||
|
Alternatively, before running the script, you can create a file named `cameras.sql`
|
||||||
|
in the same directory as the `prep.sh` script and it will be automatically
|
||||||
|
included for you.
|
||||||
|
For instructions, you can skip to "[Camera configuration and hard disk mounting](#camera)".
|
||||||
|
|
||||||
|
Once prerequisites are installed, Moonfire NVR can be built as follows:
|
||||||
|
|
||||||
|
$ yarn
|
||||||
|
$ yarn build
|
||||||
|
$ cargo test
|
||||||
|
$ cargo build --release
|
||||||
|
$ sudo install -m 755 target/release/moonfire-nvr /usr/local/bin
|
||||||
|
$ sudo mkdir /usr/local/lib/moonfire-nvr
|
||||||
|
$ sudo cp -R ui-dist /usr/local/lib/moonfire-nvr/ui
|
||||||
|
|
||||||
|
## Further configuration
|
||||||
|
|
||||||
|
Moonfire NVR should be run under a dedicated user. It keeps two kinds of
|
||||||
|
state:
|
||||||
|
|
||||||
|
* a SQLite database, typically <1 GiB. It should be stored on flash if
|
||||||
|
available.
|
||||||
|
* the "sample file directory", which holds the actual samples/frames of
|
||||||
|
H.264 video. This should be quite large and typically is stored on a hard
|
||||||
|
drive.
|
||||||
|
|
||||||
|
(See [schema.md](schema.md) for more information.)
|
||||||
|
|
||||||
|
Both kinds of state are intended to be accessed only by Moonfire NVR itself.
|
||||||
|
However, the interface for adding new cameras is not yet written, so you will
|
||||||
|
have to manually insert cameras with the `sqlite3` command line tool prior to
|
||||||
|
starting Moonfire NVR.
|
||||||
|
|
||||||
|
Manual commands would look something like this:
|
||||||
|
|
||||||
|
$ sudo addgroup --system moonfire-nvr
|
||||||
|
$ sudo adduser --system moonfire-nvr --home /var/lib/moonfire-nvr
|
||||||
|
$ sudo mkdir /var/lib/moonfire-nvr
|
||||||
|
$ sudo -u moonfire-nvr -H mkdir db sample
|
||||||
|
$ sudo -u moonfire-nvr moonfire-nvr init
|
||||||
|
|
||||||
|
### <a name="cameras"></a>Camera configuration and hard drive mounting
|
||||||
|
|
||||||
|
If a dedicated hard drive is available, set up the mount point:
|
||||||
|
|
||||||
|
$ sudo vim /etc/fstab
|
||||||
|
$ sudo mount /var/lib/moonfire-nvr/sample
|
||||||
|
|
||||||
|
Once setup is complete, it is time to add camera configurations to the
|
||||||
|
database. If the daemon is running, you will need to stop it temporarily:
|
||||||
|
|
||||||
|
$ sudo systemctl stop moonfire-nvr
|
||||||
|
|
||||||
|
You can configure the system through a text-based user interface:
|
||||||
|
|
||||||
|
$ sudo -u moonfire-nvr moonfire-nvr config 2>debug-log
|
||||||
|
|
||||||
|
In the user interface, add your cameras under the "Edit cameras" dialog.
|
||||||
|
There's a "Test" button to verify your settings directly from the dialog.
|
||||||
|
|
||||||
|
After the cameras look correct, go to "Edit retention" to assign disk space to
|
||||||
|
each camera. Leave a little slack (at least 100 MB per camera) between the total
|
||||||
|
limit and the filesystem capacity, even if you store nothing else on the disk.
|
||||||
|
There are several reasons this is needed:
|
||||||
|
|
||||||
|
* The limit currently controls fully-written files only. There will be up
|
||||||
|
to two minutes of video per camera of additional video.
|
||||||
|
* The rotation happens after the limit is exceeded, not proactively.
|
||||||
|
* Moonfire NVR currently doesn't account for the unused space in the final
|
||||||
|
filesystem block at the end of each file.
|
||||||
|
* Moonfire NVR doesn't account for the space used for directory listings.
|
||||||
|
* If a file is open when it is deleted (such as if a HTTP client is
|
||||||
|
downloading it), it stays around until the file is closed. Moonfire NVR
|
||||||
|
currently doesn't account for this.
|
||||||
|
|
||||||
|
When finished, start the daemon:
|
||||||
|
|
||||||
|
$ sudo systemctl start moonfire-nvr
|
||||||
|
|
||||||
|
### System Service
|
||||||
|
|
||||||
|
Moonfire NVR can be run as a systemd service. If you used `prep.sh` this has
|
||||||
|
been done for you. If not, Create
|
||||||
|
`/etc/systemd/system/moonfire-nvr.service`:
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=Moonfire NVR
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/moonfire-nvr run \
|
||||||
|
--db-dir=/var/lib/moonfire-nvr/db \
|
||||||
|
--http-addr=0.0.0.0:8080
|
||||||
|
Environment=TZ=:/etc/localtime
|
||||||
|
Environment=MOONFIRE_FORMAT=google-systemd
|
||||||
|
Environment=MOONFIRE_LOG=info
|
||||||
|
Environment=RUST_BACKTRACE=1
|
||||||
|
Type=simple
|
||||||
|
User=moonfire-nvr
|
||||||
|
Nice=-20
|
||||||
|
Restart=on-abnormal
|
||||||
|
CPUAccounting=true
|
||||||
|
MemoryAccounting=true
|
||||||
|
BlockIOAccounting=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
||||||
|
Note that the HTTP port currently has no authentication, encryption, or
|
||||||
|
logging; it should not be directly exposed to the Internet.
|
||||||
|
|
||||||
|
Complete the installation through `systemctl` commands:
|
||||||
|
|
||||||
|
$ sudo systemctl daemon-reload
|
||||||
|
$ sudo systemctl start moonfire-nvr
|
||||||
|
$ sudo systemctl status moonfire-nvr
|
||||||
|
$ sudo systemctl enable moonfire-nvr
|
||||||
|
|
||||||
|
See the [systemd](http://www.freedesktop.org/wiki/Software/systemd/)
|
||||||
|
documentation for more information. The [manual
|
||||||
|
pages](http://www.freedesktop.org/software/systemd/man/) for `systemd.service`
|
||||||
|
and `systemctl` may be of particular interest.
|
@ -13,84 +13,17 @@ edge version from the command line via git:
|
|||||||
## Building from source
|
## Building from source
|
||||||
|
|
||||||
There are no binary packages of Moonfire NVR available yet, so it must be built
|
There are no binary packages of Moonfire NVR available yet, so it must be built
|
||||||
from source.
|
from source. To do so, you can follow two paths:
|
||||||
|
|
||||||
Moonfire NVR is written in the [Rust Programming
|
* Scripted install: You will run some shell scripts (after preparing one or
|
||||||
Language](https://www.rust-lang.org/en-US/). In the long term, I expect this
|
two files, and will be completely done. This is by far the easiest option,
|
||||||
will result in a more secure, full-featured, easy-to-install software.
|
in particular for first time builders/installers. This process is fully
|
||||||
|
described. Read more in [Easy Installation](easy-install.md)
|
||||||
|
* Manual build and install. This is explained in detail in these
|
||||||
|
[instructions](install-manual.md)
|
||||||
|
|
||||||
You will need the following C libraries installed:
|
Regardless of the approach for setup and installation above that you choose,
|
||||||
|
please read the further configuration instructions below.
|
||||||
* [ffmpeg](http://ffmpeg.org/) version 2.x or 3.x, including `libavutil`,
|
|
||||||
`libavcodec` (to inspect H.264 frames), and `libavformat` (to connect to RTSP
|
|
||||||
servers and write `.mp4` files).
|
|
||||||
|
|
||||||
Note ffmpeg library versions older than 55.1.101, along with all versions of
|
|
||||||
the competing project [libav](http://libav.org), don't not support socket
|
|
||||||
timeouts for RTSP. For reliable reconnections on error, it's strongly
|
|
||||||
recommended to use ffmpeg library versions >= 55.1.101.
|
|
||||||
|
|
||||||
* [SQLite3](https://www.sqlite.org/).
|
|
||||||
|
|
||||||
* [`ncursesw`](https://www.gnu.org/software/ncurses/), the UTF-8 version of
|
|
||||||
the `ncurses` library.
|
|
||||||
|
|
||||||
On recent Ubuntu or Raspbian, the following command will install
|
|
||||||
all non-Rust dependencies:
|
|
||||||
|
|
||||||
$ sudo apt-get install \
|
|
||||||
build-essential \
|
|
||||||
libavcodec-dev \
|
|
||||||
libavformat-dev \
|
|
||||||
libavutil-dev \
|
|
||||||
libncurses5-dev \
|
|
||||||
libncursesw5-dev \
|
|
||||||
libsqlite3-dev \
|
|
||||||
libssl-dev \
|
|
||||||
pkgconf
|
|
||||||
|
|
||||||
Next, you need Rust 1.17+ and Cargo. The easiest way to install them is by following
|
|
||||||
the instructions at [rustup.rs](https://www.rustup.rs/).
|
|
||||||
|
|
||||||
Finally, building the UI requires [yarn](https://yarnpkg.com/en/).
|
|
||||||
|
|
||||||
You can continue to follow the build/install instructions below for a manual
|
|
||||||
build and install, or alternatively you can run the prep script called `prep.sh`.
|
|
||||||
|
|
||||||
$ cd moonfire-nvr
|
|
||||||
$ ./prep.sh
|
|
||||||
|
|
||||||
The script will take the following command line options, should you need them:
|
|
||||||
|
|
||||||
* `-S`: Skip updating and installing dependencies through apt-get. This too can be
|
|
||||||
useful on repeated builds.
|
|
||||||
|
|
||||||
You can edit variables at the start of the script to influence names and
|
|
||||||
directories, but defaults should suffice in most cases. For details refer to
|
|
||||||
the script itself. We will mention just one option, needed when you follow the
|
|
||||||
suggestion to separate database and samples between flash storage and a hard disk.
|
|
||||||
If you have the hard disk mounted on, lets say `/media/nvr`, and you want to
|
|
||||||
store the video samples inside a directory named `samples` there, you would set:
|
|
||||||
|
|
||||||
SAMPLES_DIR=/media/nvr/samples
|
|
||||||
|
|
||||||
The script will perform all necessary steps to leave you with a fully built,
|
|
||||||
installed moonfire-nvr binary. The only thing
|
|
||||||
you'll have to do manually is add your camera configuration(s) to the database.
|
|
||||||
Alternatively, before running the script, you can create a file named `cameras.sql`
|
|
||||||
in the same directory as the `prep.sh` script and it will be automatically
|
|
||||||
included for you.
|
|
||||||
For instructions, you can skip to "[Camera configuration and hard disk mounting](#camera)".
|
|
||||||
|
|
||||||
Once prerequisites are installed, Moonfire NVR can be built as follows:
|
|
||||||
|
|
||||||
$ yarn
|
|
||||||
$ yarn build
|
|
||||||
$ cargo test
|
|
||||||
$ cargo build --release
|
|
||||||
$ sudo install -m 755 target/release/moonfire-nvr /usr/local/bin
|
|
||||||
$ sudo mkdir /usr/local/lib/moonfire-nvr
|
|
||||||
$ sudo cp -R ui-dist /usr/local/lib/moonfire-nvr/ui
|
|
||||||
|
|
||||||
## Further configuration
|
## Further configuration
|
||||||
|
|
||||||
@ -103,13 +36,13 @@ state:
|
|||||||
H.264 video. These should be quite large and are typically stored on hard
|
H.264 video. These should be quite large and are typically stored on hard
|
||||||
drives.
|
drives.
|
||||||
|
|
||||||
|
Both states are intended to be accessed by moonfire-nvr only, but can be
|
||||||
|
changed after building. See below.
|
||||||
(See [schema.md](schema.md) for more information.)
|
(See [schema.md](schema.md) for more information.)
|
||||||
|
|
||||||
Both kinds of state are intended to be accessed only by Moonfire NVR itself.
|
The database changes and sample file directory changes require the moonfire-nvr
|
||||||
However, the interface for adding new cameras is not yet written, so you will
|
binary to be built, so can only be done after completing the build. The other
|
||||||
have to manually insert cameras with the `sqlite3` command line tool prior to
|
settings and preparations should be done before building.
|
||||||
starting Moonfire NVR.
|
|
||||||
|
|
||||||
Manual commands would look something like this:
|
Manual commands would look something like this:
|
||||||
|
|
||||||
$ sudo addgroup --system moonfire-nvr
|
$ sudo addgroup --system moonfire-nvr
|
||||||
@ -118,7 +51,7 @@ Manual commands would look something like this:
|
|||||||
$ sudo -u moonfire-nvr -H mkdir db sample
|
$ sudo -u moonfire-nvr -H mkdir db sample
|
||||||
$ sudo -u moonfire-nvr moonfire-nvr init
|
$ sudo -u moonfire-nvr moonfire-nvr init
|
||||||
|
|
||||||
### <a name="cameras"></a>Camera configuration and hard drive mounting
|
### <a name="drive mounting"></a>Camera configuration and hard drive mounting
|
||||||
|
|
||||||
If a dedicated hard drive is available, set up the mount point:
|
If a dedicated hard drive is available, set up the mount point:
|
||||||
|
|
||||||
@ -130,7 +63,7 @@ database. If the daemon is running, you will need to stop it temporarily:
|
|||||||
|
|
||||||
$ sudo systemctl stop moonfire-nvr
|
$ sudo systemctl stop moonfire-nvr
|
||||||
|
|
||||||
You can configure the system through a text-based user interface:
|
You can configure the system's database through a text-based user interface:
|
||||||
|
|
||||||
$ sudo -u moonfire-nvr moonfire-nvr config 2>debug-log
|
$ sudo -u moonfire-nvr moonfire-nvr config 2>debug-log
|
||||||
|
|
||||||
|
719
moonfire.sublime-workspace
Normal file
719
moonfire.sublime-workspace
Normal file
@ -0,0 +1,719 @@
|
|||||||
|
{
|
||||||
|
"auto_complete":
|
||||||
|
{
|
||||||
|
"selected_items":
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"_recordin",
|
||||||
|
"_recordingsRange"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"_record",
|
||||||
|
"_recordingsRange"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"selec",
|
||||||
|
"selectedRange"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"jquer",
|
||||||
|
"jqueryUIBundleModules"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"rec",
|
||||||
|
"recordingClickHandler"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"recordin",
|
||||||
|
"recordingsView"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"timeF",
|
||||||
|
"timeFormatter"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"timeSta",
|
||||||
|
"formatTimeStamp90k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"form",
|
||||||
|
"formatTime90k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ret",
|
||||||
|
"returns\t@returns"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"webpack",
|
||||||
|
"webpackConfig2"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"webPackConf",
|
||||||
|
"webpackConfig2"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"project",
|
||||||
|
"projectRoot"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"prject",
|
||||||
|
"projectRoot"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"datepi",
|
||||||
|
"datepicker"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"_time",
|
||||||
|
"_timeFormatter"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"topi",
|
||||||
|
"toPickerTimeId"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"fromP",
|
||||||
|
"fromPickerTimeId"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"toP",
|
||||||
|
"toPickerId"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"star",
|
||||||
|
"startTime90k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"record",
|
||||||
|
"recordingsView"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"update",
|
||||||
|
"updateRecordings"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"new",
|
||||||
|
"newTimeFormat"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"formatT",
|
||||||
|
"formatTime90K"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"_camer",
|
||||||
|
"_cameraRange"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"trimmed",
|
||||||
|
"trimmedAgainst"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"dur",
|
||||||
|
"duration90k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"formatTim",
|
||||||
|
"formatTimeStamp90K"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"le",
|
||||||
|
"length"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"displa",
|
||||||
|
"displayObjects"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"checkb",
|
||||||
|
"checkboxSelector"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"dom",
|
||||||
|
"domElement"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"came",
|
||||||
|
"cameraCheckBoxDOM"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"camera",
|
||||||
|
"cameraJson"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"to",
|
||||||
|
"totalSampleFileBytes"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"vide",
|
||||||
|
"videoSampleEntryHeight"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"vieo",
|
||||||
|
"videoSampleEntryWidth"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"cam",
|
||||||
|
"cameraJson"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Recordin",
|
||||||
|
"RecordingY"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Js",
|
||||||
|
"jsonwrapper"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"end",
|
||||||
|
"endTime90k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"js",
|
||||||
|
"jsonData"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"recor",
|
||||||
|
"recordingJson"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"start",
|
||||||
|
"startTime90k"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ra",
|
||||||
|
"rate"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"root",
|
||||||
|
"rootFinder"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Prod_Med",
|
||||||
|
"Prod_Media_PRF"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Prod_Bill",
|
||||||
|
"Prod_Billing_Portal"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Prod_C",
|
||||||
|
"Prod_CSP_BD"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Prod_M",
|
||||||
|
"Prod_Media"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Prod_DA",
|
||||||
|
"Prod_DASRT"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Plat",
|
||||||
|
"PlatQA"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Pro",
|
||||||
|
"Prod_DDC"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Prod",
|
||||||
|
"Prod_DDC"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Pr",
|
||||||
|
"Prod_DDC"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Collee",
|
||||||
|
"ColleenK"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Coll",
|
||||||
|
"ColleenK"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ISab",
|
||||||
|
"ISabine"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ro",
|
||||||
|
"roam_conf"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"_debugCe",
|
||||||
|
"_debugCellValue"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"debug",
|
||||||
|
"debugCellValue"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zami",
|
||||||
|
"ZamiLoadSensors"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"execSes",
|
||||||
|
"execSensorsCalibrateZero"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"buffers":
|
||||||
|
[
|
||||||
|
],
|
||||||
|
"build_system": "",
|
||||||
|
"build_system_choices":
|
||||||
|
[
|
||||||
|
],
|
||||||
|
"build_varint": "",
|
||||||
|
"command_palette":
|
||||||
|
{
|
||||||
|
"height": 359.0,
|
||||||
|
"last_filter": "pack ins ",
|
||||||
|
"selected_items":
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"pack ins ",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pac list",
|
||||||
|
"Package Control: List Packages"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pac ins",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pack rem",
|
||||||
|
"Package Control: Remove Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pack list",
|
||||||
|
"Package Control: List Packages"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pack ins",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pac inst",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pac li",
|
||||||
|
"Package Control: List Packages"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pac re",
|
||||||
|
"Package Control: Remove Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pack li",
|
||||||
|
"Package Control: List Packages"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"insta",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"package",
|
||||||
|
"Package Control: Remove Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
" instal",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"pack",
|
||||||
|
"Preferences: Browse Packages"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"inst",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"rem",
|
||||||
|
"Package Control: Remove Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"remove",
|
||||||
|
"Package Control: Remove Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"in",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"re",
|
||||||
|
"Package Control: Remove Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"install p",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"install",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"insall",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"instal",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"packa",
|
||||||
|
"Package Control: Install Package"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"width": 476.0
|
||||||
|
},
|
||||||
|
"console":
|
||||||
|
{
|
||||||
|
"height": 322.0,
|
||||||
|
"history":
|
||||||
|
[
|
||||||
|
"import urllib.request,os,hashlib; h = '6f4c264a24d933ce70df5dedcf1dcaee' + 'ebe013ee18cced0ef93d5f746d80ef60'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)",
|
||||||
|
"import urllib.request,os,hashlib; h = 'eb2297e1a458f27d836c04bb0cbaf282' + 'd0e7a3098092775ccb37ca9d6b2e4b7d'; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); by = urllib.request.urlopen( 'http://packagecontrol.io/' + pf.replace(' ', '%20')).read(); dh = hashlib.sha256(by).hexdigest(); print('Error validating download (got %s instead of %s), please try manual install' % (dh, h)) if dh != h else open(os.path.join( ipp, pf), 'wb' ).write(by)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"distraction_free":
|
||||||
|
{
|
||||||
|
"menu_visible": true,
|
||||||
|
"show_minimap": false,
|
||||||
|
"show_open_files": false,
|
||||||
|
"show_tabs": false,
|
||||||
|
"side_bar_visible": false,
|
||||||
|
"status_bar_visible": false
|
||||||
|
},
|
||||||
|
"file_history":
|
||||||
|
[
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/models/Range.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/models/Recording.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/support/RecordingFormatter.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/models/Range90k.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/views/RecordingsView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/views/CameraView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/models/CalendarTSRange.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/views/NVRSettingsView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/NVRApplication.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/index.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/MoonfireAPI.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/assets/index.html",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/views/DatePickerView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/views/CheckboxGroupView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/views/CalendarView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/support/URLBuilder.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/support/TimeFormatter.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/support/Time90kParser.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/webpack/parts/Settings.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/webpack/base.config.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/webpack/dev.config.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/webpack/NVRSettings.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/webpack/prod.config.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/settings-nvr-local.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/settings-nvr.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/models/Camera.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/models/JsonWrapper.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/node_modules/jquery-ui/ui/widgets/datepicker.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-dist/vendor.bundle.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-dist/nvr.bundle.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/assets/index.css",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/HTMLBeautify/HTMLBeautify.sublime-settings",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/moonfire.sublime-project",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/User/ESLint-Formatter.sublime-settings",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/ESLint-Formatter/ESLint-Formatter.sublime-settings",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/.eslintrc.json",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/.prettierrc.json",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/ui-src/lib/IteratorUtils.js",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/JsPrettier/JsPrettier.sublime-settings",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/User/JsPrettier.sublime-settings",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/JsFormat/JsFormat.sublime-settings",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/guide/easy-install.md",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/guide/developing-ui.md",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire-nvr/README.md",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/User/Preferences.sublime-settings",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/Default/Preferences.sublime-settings",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/views/DatePickerView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/TimeFormatter.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/models/Camera.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/Time90kParser.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/models/CalendarTSRange.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/index.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/RecordingFormatter.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/views/CalendarView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/views/IteratorUtils.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/models/Range.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/views/CameraView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/views/RecordingsView.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/index.html",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/models/Range90k.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/models/Recording.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/recordingformatter.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/views/RecordingsDisplay.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/webpack/base.config.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/webpack/dev.config.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/jsonwrapper.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/recordingsdisplay.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/recording.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/camera.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/range90k.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/lib/cameradisplay.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/recordingsdisplay.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/webpack/prod.config.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/camera.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/recordingstable.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/recordingformatter.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/jsonwrapper.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/package.json",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/webpack.config.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/ui-src/recording.js",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/.eslintrc.js",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/User/JsFormat.sublime-settings",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/moonfire/webpack/full.config.js",
|
||||||
|
"/Users/dolf/Desktop/git config",
|
||||||
|
"/Users/dolf/Desktop/Signatures/dolf-simple.html",
|
||||||
|
"/Users/dolf/Desktop/Signatures/reg-simple.html",
|
||||||
|
"/Users/dolf/Desktop/dolf.vcf",
|
||||||
|
"/Users/dolf/Desktop/Dolf HTML.html",
|
||||||
|
"/Users/dolf/Desktop/Signatures/sig/dolf-home.vcf",
|
||||||
|
"/Users/dolf/Desktop/Signatures/dolf-home.html",
|
||||||
|
"/Users/dolf/Desktop/Starfield HTML.html",
|
||||||
|
"/Users/dolf/Desktop/Signatures/starfield-medium.html",
|
||||||
|
"/Users/dolf/Desktop/Signatures/reg-medium.html",
|
||||||
|
"/Users/dolf/Desktop/Signatures/dolf-medium.html",
|
||||||
|
"/Users/dolf/Desktop/Signatures/dolf-dolf.html",
|
||||||
|
"/Users/dolf/Desktop/recipients.txt",
|
||||||
|
"/Users/dolf/Desktop/t2.txt",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/HTML-CSS-JS Prettify/.jsbeautifyrc.defaults.json",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/User/.jsbeautifyrc",
|
||||||
|
"/Users/dolf/Desktop/x.js",
|
||||||
|
"/Users/dolf/Documents/Dolf-Sites/starreveld.com/index.html",
|
||||||
|
"/Users/dolf/Desktop/types.txt",
|
||||||
|
"/Users/dolf/Desktop/t.txt",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/User/JavascriptBeautify.sublime-settings",
|
||||||
|
"/Users/dolf/Library/Application Support/Sublime Text 3/Packages/Javascript Beautify/JavascriptBeautify.sublime-settings",
|
||||||
|
"/Users/dolf/Documents/Dolf-Sites/starreveld.com/Scripts/mmenudom.js",
|
||||||
|
"/Users/dolf/Documents/Dolf-Sites/starreveld.com/crossdomain.xml",
|
||||||
|
"/Users/dolf/Downloads/weather.dms",
|
||||||
|
"/Users/dolf/hta.txt",
|
||||||
|
"/Users/dolf/Desktop/x/Roots/FunctionNext.java",
|
||||||
|
"/Users/dolf/Desktop/x/Roots/Interval.java",
|
||||||
|
"/Users/dolf/Desktop/x/Roots/Function1.java",
|
||||||
|
"/Users/dolf/Desktop/x/RootsMain.java",
|
||||||
|
"/Users/dolf/Documents/Dev/Dolf/ubnt-logview/README",
|
||||||
|
"/Users/dolf/Desktop/goldens.dot",
|
||||||
|
"/Users/dolf/Documents/Dev/Anderson/READ ME",
|
||||||
|
"/Users/dolf/Desktop/x/Animals/Dog.java",
|
||||||
|
"/Users/dolf/Desktop/x/Animals/IAnimal.java",
|
||||||
|
"/Users/dolf/Desktop/x/Animals/Animal.java",
|
||||||
|
"/Users/dolf/Desktop/x/Animals/Cat.java",
|
||||||
|
"/Users/dolf/Desktop/x/AniMain.java",
|
||||||
|
"/Users/dolf/Desktop/x/Animals/Crate.java",
|
||||||
|
"/Users/dolf/Dropbox/Dolf/Dropbox/Akamai/Drawings/akamai.dot",
|
||||||
|
"/Users/dolf/Desktop/x/Animals/BaseAnimal.java",
|
||||||
|
"/Users/dolf/Desktop/T2.java",
|
||||||
|
"/Users/dolf/Desktop/T1.java",
|
||||||
|
"/Users/dolf/Desktop/main.java",
|
||||||
|
"/Users/dolf/Downloads/MTR"
|
||||||
|
],
|
||||||
|
"find":
|
||||||
|
{
|
||||||
|
"height": 39.0
|
||||||
|
},
|
||||||
|
"find_in_files":
|
||||||
|
{
|
||||||
|
"height": 101.0,
|
||||||
|
"where_history":
|
||||||
|
[
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"find_state":
|
||||||
|
{
|
||||||
|
"case_sensitive": false,
|
||||||
|
"find_history":
|
||||||
|
[
|
||||||
|
"Recording ra",
|
||||||
|
"Trim ",
|
||||||
|
"\\'Trim",
|
||||||
|
"'Trim ",
|
||||||
|
"trim range",
|
||||||
|
"Range90k",
|
||||||
|
"Camera range",
|
||||||
|
"_updateRecordings",
|
||||||
|
"updateRecord",
|
||||||
|
"recordingsRange",
|
||||||
|
"fetch",
|
||||||
|
"_updateRecordings",
|
||||||
|
"onTrimChange",
|
||||||
|
"onSelec",
|
||||||
|
"trimHandler",
|
||||||
|
"addOnedateOnlyThenEndOfDay",
|
||||||
|
"parseDateTime90k",
|
||||||
|
"\\[description\\]",
|
||||||
|
"[description]",
|
||||||
|
"console.log",
|
||||||
|
"new range",
|
||||||
|
"recordingsUrl\\(",
|
||||||
|
"recordingsUrl(",
|
||||||
|
"_videoLength",
|
||||||
|
"_videoLengthHandler",
|
||||||
|
"onVideoLengthChange",
|
||||||
|
"fetch(",
|
||||||
|
"onTimeCh",
|
||||||
|
"videoLength",
|
||||||
|
"fetch\\(",
|
||||||
|
"fetch(",
|
||||||
|
"#split",
|
||||||
|
"fetch(",
|
||||||
|
"onVideoLengthChange",
|
||||||
|
"_videoLengthHandler",
|
||||||
|
"videoLengthE",
|
||||||
|
"_trimHandler",
|
||||||
|
"onTrimChange",
|
||||||
|
"initializeWith",
|
||||||
|
"setupCalendar",
|
||||||
|
"onTrimChange",
|
||||||
|
"dsiabl",
|
||||||
|
"_pickerTimeChanged",
|
||||||
|
"_updateRangeDates",
|
||||||
|
"_setRangeTime",
|
||||||
|
"new time",
|
||||||
|
"_informRangeChange",
|
||||||
|
"_setRangetim",
|
||||||
|
"timeFmt",
|
||||||
|
"('.*'),",
|
||||||
|
"dialog",
|
||||||
|
"import",
|
||||||
|
"require",
|
||||||
|
"dialog",
|
||||||
|
"jquery-ui/themes/base/button.css",
|
||||||
|
"('.*'),",
|
||||||
|
"jquery-ui/themes/base/button.css",
|
||||||
|
"<<",
|
||||||
|
"process.env.NODE_ENV",
|
||||||
|
"'NVR",
|
||||||
|
"NVR",
|
||||||
|
"recording cli"
|
||||||
|
],
|
||||||
|
"highlight": true,
|
||||||
|
"in_selection": false,
|
||||||
|
"preserve_case": false,
|
||||||
|
"regex": true,
|
||||||
|
"replace_history":
|
||||||
|
[
|
||||||
|
"import(/* webpackChunkName: \"jquery\" */ $1);",
|
||||||
|
"require($1);"
|
||||||
|
],
|
||||||
|
"reverse": false,
|
||||||
|
"show_context": false,
|
||||||
|
"use_buffer2": false,
|
||||||
|
"whole_word": false,
|
||||||
|
"wrap": true
|
||||||
|
},
|
||||||
|
"groups":
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"sheets":
|
||||||
|
[
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"incremental_find":
|
||||||
|
{
|
||||||
|
"height": 23.0
|
||||||
|
},
|
||||||
|
"input":
|
||||||
|
{
|
||||||
|
"height": 35.0
|
||||||
|
},
|
||||||
|
"layout":
|
||||||
|
{
|
||||||
|
"cells":
|
||||||
|
[
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"cols":
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
"rows":
|
||||||
|
[
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"menu_visible": true,
|
||||||
|
"output.SublimeLinter":
|
||||||
|
{
|
||||||
|
"height": 0.0
|
||||||
|
},
|
||||||
|
"output.SublimeLinter Messages":
|
||||||
|
{
|
||||||
|
"height": 102.0
|
||||||
|
},
|
||||||
|
"output.find_results":
|
||||||
|
{
|
||||||
|
"height": 300.0
|
||||||
|
},
|
||||||
|
"output.unsaved_changes":
|
||||||
|
{
|
||||||
|
"height": 102.0
|
||||||
|
},
|
||||||
|
"pinned_build_system": "",
|
||||||
|
"project": "moonfire.sublime-project",
|
||||||
|
"replace":
|
||||||
|
{
|
||||||
|
"height": 70.0
|
||||||
|
},
|
||||||
|
"save_all_on_build": true,
|
||||||
|
"select_file":
|
||||||
|
{
|
||||||
|
"height": 0.0,
|
||||||
|
"last_filter": "",
|
||||||
|
"selected_items":
|
||||||
|
[
|
||||||
|
],
|
||||||
|
"width": 0.0
|
||||||
|
},
|
||||||
|
"select_project":
|
||||||
|
{
|
||||||
|
"height": 0.0,
|
||||||
|
"last_filter": "",
|
||||||
|
"selected_items":
|
||||||
|
[
|
||||||
|
],
|
||||||
|
"width": 0.0
|
||||||
|
},
|
||||||
|
"select_symbol":
|
||||||
|
{
|
||||||
|
"height": 0.0,
|
||||||
|
"last_filter": "",
|
||||||
|
"selected_items":
|
||||||
|
[
|
||||||
|
],
|
||||||
|
"width": 0.0
|
||||||
|
},
|
||||||
|
"selected_group": 0,
|
||||||
|
"settings":
|
||||||
|
{
|
||||||
|
},
|
||||||
|
"show_minimap": true,
|
||||||
|
"show_open_files": false,
|
||||||
|
"show_tabs": true,
|
||||||
|
"side_bar_visible": true,
|
||||||
|
"side_bar_width": 205.0,
|
||||||
|
"status_bar_visible": true,
|
||||||
|
"template_settings":
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
18
package.json
18
package.json
@ -4,7 +4,8 @@
|
|||||||
"url": "https://github.com/scottlamb/moonfire-nvr/issues"
|
"url": "https://github.com/scottlamb/moonfire-nvr/issues"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "webpack && ln -f ui-src/index.html ui-dist/"
|
"start": "webpack-dev-server --mode development --config webpack/dev.config.js --progress",
|
||||||
|
"build": "webpack --mode production --config webpack/prod.config.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery": "^3.2.1",
|
"jquery": "^3.2.1",
|
||||||
@ -18,12 +19,17 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.4",
|
||||||
"babel-minify-webpack-plugin": "^0.2.0",
|
"babel-minify-webpack-plugin": "^0.3.0",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-preset-env": "^1.6.1",
|
||||||
"css-loader": "^0.28.7",
|
"clean-webpack-plugin": "^0.1.18",
|
||||||
"file-loader": "^1.1.5",
|
"css-loader": "^0.28.10",
|
||||||
|
"file-loader": "^1.1.11",
|
||||||
|
"html-webpack-plugin": "^3.0.6",
|
||||||
"style-loader": "^0.19.0",
|
"style-loader": "^0.19.0",
|
||||||
"webpack": "^3.8.1"
|
"webpack": "^4.0.1",
|
||||||
|
"webpack-cli": "^2.0.10",
|
||||||
|
"webpack-dev-server": "^3.1.0",
|
||||||
|
"webpack-merge": "^4.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
278
prep.sh
278
prep.sh
@ -1,278 +0,0 @@
|
|||||||
#!/bin/bash -x
|
|
||||||
#
|
|
||||||
# This file is part of Moonfire NVR, a security camera network video recorder.
|
|
||||||
# Copyright (C) 2016 Scott Lamb <slamb@slamb.org>
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# In addition, as a special exception, the copyright holders give
|
|
||||||
# permission to link the code of portions of this program with the
|
|
||||||
# OpenSSL library under certain conditions as described in each
|
|
||||||
# individual source file, and distribute linked combinations including
|
|
||||||
# the two.
|
|
||||||
#
|
|
||||||
# You must obey the GNU General Public License in all respects for all
|
|
||||||
# of the code used other than OpenSSL. If you modify file(s) with this
|
|
||||||
# exception, you may extend this exception to your version of the
|
|
||||||
# file(s), but you are not obligated to do so. If you do not wish to do
|
|
||||||
# so, delete this exception statement from your version. If you delete
|
|
||||||
# this exception statement from all source files in the program, then
|
|
||||||
# also delete it here.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# Script to prepare for moonfire-nvr operations
|
|
||||||
#
|
|
||||||
# Command line options:
|
|
||||||
# -S: Skip apt-get update and install
|
|
||||||
#
|
|
||||||
|
|
||||||
# Configuration variables. Should only need minimal, or zero, changes.
|
|
||||||
# Empty values will use defaults. For convenience, we attempt to read
|
|
||||||
# customizations from prep.config first
|
|
||||||
#
|
|
||||||
if [ -r prep.config ]; then
|
|
||||||
. prep.config
|
|
||||||
fi
|
|
||||||
|
|
||||||
# User and group
|
|
||||||
# Default: or whatever is in $NVR_USER (default "moonfire-nvr")
|
|
||||||
#
|
|
||||||
#NVR_USER=
|
|
||||||
#NVR_GROUP=
|
|
||||||
|
|
||||||
# Port for web server
|
|
||||||
# Default: 8080
|
|
||||||
#
|
|
||||||
#NVR_PORT=
|
|
||||||
|
|
||||||
# This should, ideally, be a location on flash storage under which the
|
|
||||||
# moonfire user's home directory will be created.
|
|
||||||
# Default: "/var/lib"
|
|
||||||
#
|
|
||||||
#NVR_HOME_BASE=
|
|
||||||
|
|
||||||
# Set to mountpoint of media directory, empty to stay in home directory
|
|
||||||
# Default: empty
|
|
||||||
#SAMPLES_DIR=/media/nvr/samples
|
|
||||||
|
|
||||||
# Set to path for media directory relative to mountpoint
|
|
||||||
# Default: "samples"
|
|
||||||
#
|
|
||||||
#SAMPLES_DIR_NAME=
|
|
||||||
|
|
||||||
# Binary location
|
|
||||||
# Default: "/usr/local/bin/moonfire-nvr"
|
|
||||||
#
|
|
||||||
#SERVICE_BIN=
|
|
||||||
|
|
||||||
# Resource files location
|
|
||||||
# Default: "/usr/local/lib/moonfire-nvr"
|
|
||||||
#LIB_DIR=/usr/local/lib/moonfire-nvr
|
|
||||||
|
|
||||||
# Service name
|
|
||||||
# Default: "moonfire-nvr"
|
|
||||||
#
|
|
||||||
#SERVICE_NAME=
|
|
||||||
|
|
||||||
# Service Description
|
|
||||||
# Default: "Moonfire NVR"
|
|
||||||
#
|
|
||||||
#SERVICE_DESC=
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
|
||||||
# Derived variables: Do not modify!
|
|
||||||
#
|
|
||||||
# Determine directory path of this script
|
|
||||||
#
|
|
||||||
SOURCE="${BASH_SOURCE[0]}"
|
|
||||||
while [ -h "$SOURCE" ]; do # resolve $SOURCE until file no longer a symlink
|
|
||||||
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
|
|
||||||
SOURCE="$(readlink "$SOURCE")"
|
|
||||||
# if $SOURCE was relative symlink, resolve relative to path of symlink
|
|
||||||
[[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
|
|
||||||
done
|
|
||||||
|
|
||||||
SRC_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"/src
|
|
||||||
NVR_USER="${NVR_USER:-moonfire-nvr}"
|
|
||||||
NVR_GROUP="${NVR_GROUP:-$NVR_USER}"
|
|
||||||
NVR_PORT="${NVR_PORT:-8080}"
|
|
||||||
NVR_HOME_BASE="${NVR_HOME_BASE:-/var/lib}"
|
|
||||||
NVR_HOME="${NVR_HOME_BASE}/${NVR_USER}"
|
|
||||||
DB_DIR="${DB_DIR:-$NVR_HOME/db}"
|
|
||||||
SAMPLES_DIR_NAME="${SAMPLES_DIR_NAME:-samples}"
|
|
||||||
SAMPLES_DIR="${SAMPLES_DIR:-$NVR_HOME/$SAMPLES_DIR_NAME}"
|
|
||||||
SERVICE_NAME="${SERVICE_NAME:-moonfire-nvr}"
|
|
||||||
SERVICE_DESC="${SERVICE_DESC:-Moonfire NVR}"
|
|
||||||
SERVICE_BIN="${SERVICE_BIN:-/usr/local/bin/moonfire-nvr}"
|
|
||||||
LIB_DIR="${UI_DIR:-/usr/local/lib/moonfire-nvr}"
|
|
||||||
|
|
||||||
# Process command line options
|
|
||||||
#
|
|
||||||
while getopts ":DS" opt; do
|
|
||||||
case $opt in
|
|
||||||
S) SKIP_APT=1
|
|
||||||
;;
|
|
||||||
:)
|
|
||||||
echo "Option -$OPTARG requires an argument." >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
\?)
|
|
||||||
echo "Invalid option: -$OPTARG" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# Setup all apt packages we need
|
|
||||||
#
|
|
||||||
echo 'Preparing and downloading packages we need...'; echo
|
|
||||||
if [ "${SKIP_APT:-0}" != 1 ]; then
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install \
|
|
||||||
build-essential \
|
|
||||||
libavcodec-dev \
|
|
||||||
libavformat-dev \
|
|
||||||
libavutil-dev \
|
|
||||||
libncurses5-dev \
|
|
||||||
libncursesw5-dev \
|
|
||||||
libsqlite3-dev
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if binary is installed. Setup for build if it is not
|
|
||||||
#
|
|
||||||
if [ ! -x "${SERVICE_BIN}" ]; then
|
|
||||||
echo "Binary not installed, building..."; echo
|
|
||||||
if ! cargo --version; then
|
|
||||||
echo "cargo not installed/working."
|
|
||||||
echo "Install Rust (see http://rustup.us) first."
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! cargo test; then
|
|
||||||
echo "test failed. Try to run the following manually for more info"
|
|
||||||
echo "RUST_TEST_THREADS=1 cargo test --verbose"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! cargo build --release; then
|
|
||||||
echo "Server build failed."
|
|
||||||
echo "RUST_TEST_THREADS=1 cargo build --release --verbose"
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sudo install -m 755 target/release/moonfire-nvr ${SERVICE_BIN}
|
|
||||||
if [ -x "${SERVICE_BIN}" ]; then
|
|
||||||
echo "Binary installed..."; echo
|
|
||||||
else
|
|
||||||
echo "Build failed to install..."; echo
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [ ! -d "${LIB_DIR}/ui" ]; then
|
|
||||||
echo "UI directory doesn't exist, building..."; echo
|
|
||||||
if ! yarn --version; then
|
|
||||||
echo "yarn not installed/working."
|
|
||||||
echo "Install from https://yarnpkg.com/ first."
|
|
||||||
echo
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! yarn; then
|
|
||||||
echo "UI package installation failed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if ! yarn build; then
|
|
||||||
echo "UI build failed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sudo mkdir "${LIB_DIR}"
|
|
||||||
sudo cp -R ui-dist "${LIB_DIR}/ui"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create user and groups
|
|
||||||
#
|
|
||||||
echo 'Create user/group and directories we need...'; echo
|
|
||||||
sudo addgroup --quiet --system ${NVR_GROUP}
|
|
||||||
sudo adduser --quiet --system ${NVR_USER} --ingroup "${NVR_GROUP}" --home "${NVR_HOME}"
|
|
||||||
if [ ! -d "${NVR_HOME}" ]; then
|
|
||||||
sudo mkdir "${NVR_HOME}"
|
|
||||||
fi
|
|
||||||
sudo chown ${NVR_USER}:${NVR_GROUP} "${NVR_HOME}"
|
|
||||||
|
|
||||||
# Prepare samples directory
|
|
||||||
#
|
|
||||||
if [ -z "${SAMPLES_DIR}" ]; then
|
|
||||||
SAMPLES_PATH="${NVR_HOME}/${SAMPLES_DIR_NAME}"
|
|
||||||
if [ ! -d "${SAMPLES_PATH}" ]; then
|
|
||||||
sudo -u ${NVR_USER} -H mkdir "${SAMPLES_PATH}"
|
|
||||||
else
|
|
||||||
chown -R ${NVR_USER}.${NVR_USER} "${SAMPLES_PATH}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
SAMPLES_PATH="${SAMPLES_DIR}"
|
|
||||||
if [ ! -d "${SAMPLES_PATH}" ]; then
|
|
||||||
sudo mkdir "${SAMPLES_PATH}"
|
|
||||||
echo ">>> Do not forget to edit /etc/fstab to mount samples media"; echo
|
|
||||||
fi
|
|
||||||
chown -R ${NVR_USER}.${NVR_USER} "${SAMPLES_PATH}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
# Prepare for sqlite directory and set schema into db
|
|
||||||
#
|
|
||||||
echo 'Create database...'; echo
|
|
||||||
if [ ! -d "${DB_DIR}" ]; then
|
|
||||||
sudo -u ${NVR_USER} -H mkdir "${DB_DIR}"
|
|
||||||
fi
|
|
||||||
sudo -u ${NVR_USER} -H ${SERVICE_BIN} init --db-dir="${DB_DIR}"
|
|
||||||
|
|
||||||
# Prepare service files
|
|
||||||
#
|
|
||||||
SERVICE_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
|
|
||||||
sudo sh -c "cat > ${SERVICE_PATH}" <<NVR_EOF
|
|
||||||
[Unit]
|
|
||||||
Description=${SERVICE_DESC}
|
|
||||||
After=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=${SERVICE_BIN} run \\
|
|
||||||
--sample-file-dir=${SAMPLES_PATH} \\
|
|
||||||
--db-dir=${DB_DIR} \\
|
|
||||||
--ui-dir=${LIB_DIR}/ui \\
|
|
||||||
--http-addr=0.0.0.0:${NVR_PORT}
|
|
||||||
Environment=TZ=:/etc/localtime
|
|
||||||
Environment=MOONFIRE_FORMAT=google-systemd
|
|
||||||
Environment=MOONFIRE_LOG=info
|
|
||||||
Environment=RUST_BACKTRACE=1
|
|
||||||
Type=simple
|
|
||||||
User=${NVR_USER}
|
|
||||||
Nice=-20
|
|
||||||
Restart=on-abnormal
|
|
||||||
CPUAccounting=true
|
|
||||||
MemoryAccounting=true
|
|
||||||
BlockIOAccounting=true
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
NVR_EOF
|
|
||||||
|
|
||||||
# Configure and start service
|
|
||||||
#
|
|
||||||
if [ -f "${SERVICE_PATH}" ]; then
|
|
||||||
echo 'Configuring system daemon...'; echo
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable ${SERVICE_NAME}
|
|
||||||
sudo systemctl restart ${SERVICE_NAME}
|
|
||||||
echo 'Getting system daemon status...'; echo
|
|
||||||
sudo systemctl status ${SERVICE_NAME}
|
|
||||||
fi
|
|
111
scripts/build.sh
Executable file
111
scripts/build.sh
Executable file
@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# This file is part of Moonfire NVR, a security camera network video recorder.
|
||||||
|
# Copyright (C) 2016-17 Scott Lamb <slamb@slamb.org>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# In addition, as a special exception, the copyright holders give
|
||||||
|
# permission to link the code of portions of this program with the
|
||||||
|
# OpenSSL library under certain conditions as described in each
|
||||||
|
# individual source file, and distribute linked combinations including
|
||||||
|
# the two.
|
||||||
|
#
|
||||||
|
# You must obey the GNU General Public License in all respects for all
|
||||||
|
# of the code used other than OpenSSL. If you modify file(s) with this
|
||||||
|
# exception, you may extend this exception to your version of the
|
||||||
|
# file(s), but you are not obligated to do so. If you do not wish to do
|
||||||
|
# so, delete this exception statement from your version. If you delete
|
||||||
|
# this exception statement from all source files in the program, then
|
||||||
|
# also delete it here.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
. `dirname ${BASH_SOURCE[0]}`/script-functions.sh
|
||||||
|
|
||||||
|
initEnvironmentVars
|
||||||
|
|
||||||
|
# Process command line options
|
||||||
|
#
|
||||||
|
while getopts ":Bt" opt; do
|
||||||
|
case $opt in
|
||||||
|
B) BUILD_ONLY=1
|
||||||
|
;;
|
||||||
|
t) IGNORE_TESTS=1
|
||||||
|
;;
|
||||||
|
:)
|
||||||
|
echo_fatal -x "Option -$OPTARG requires an argument."
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
echo_fatal "Invalid option: -$OPTARG"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Setup cargo if files are present
|
||||||
|
#
|
||||||
|
initCargo
|
||||||
|
|
||||||
|
# Check environment
|
||||||
|
#
|
||||||
|
|
||||||
|
rv=$(getVersion rustc 0.0)
|
||||||
|
if ! versionAtLeast "$rv" "$RUSTC_MIN_VERSION"; then
|
||||||
|
echo_fatal -x "rustc not present or version less than $RUSTC_MIN_VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cv=$(getVersion cargo 0.0)
|
||||||
|
if ! versionAtLeast "$cv" "$CARGO_MIN_VERSION"; then
|
||||||
|
echo_fatal -x "cargo not present or version less than $CARGO_MIN_VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
yv=$(getVersion yarn 0.0)
|
||||||
|
if ! versionAtLeast "$yv" "$YARN_MIN_VERSION"; then
|
||||||
|
echo_fatal -x "yarn not present or version less than $YARN_MIN_VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Building main server component
|
||||||
|
#
|
||||||
|
if [ "${FORCE_CLEAN:-0}" -eq 1 ]; then
|
||||||
|
echo_info -x "Forcing clean server build..."
|
||||||
|
cargo clean
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo_info -x "Building test version..."
|
||||||
|
if ! cargo test; then
|
||||||
|
echo_error -x "test failed." "Try to run the following manually for more info" \
|
||||||
|
"RUST_TEST_THREADS=1 cargo test --verbose" ''
|
||||||
|
if [ "${IGNORE_TESTS:-0}" -ne 1 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if ! cargo build --release; then
|
||||||
|
echo_error -x "Server/release build failed." "Try to run the following manually for more info" \
|
||||||
|
"RUST_TEST_THREADS=1 cargo build --release --verbose" ''
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Building UI components
|
||||||
|
#
|
||||||
|
echo_info -x "Building UI components..."
|
||||||
|
if ! yarn build; then
|
||||||
|
echo_fatal -x "UI build failed." "yarn build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop if build only is desired
|
||||||
|
#
|
||||||
|
if [ "${BUILD_ONLY:-0}" != 0 ]; then
|
||||||
|
echo_info -x "Build (only) complete, exiting"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
. `dirname ${BASH_SOURCE[0]}`/install.sh
|
3
scripts/clean.sh
Executable file
3
scripts/clean.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
rm -fr node_modules
|
135
scripts/install.sh
Executable file
135
scripts/install.sh
Executable file
@ -0,0 +1,135 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# This file is part of Moonfire NVR, a security camera network video recorder.
|
||||||
|
# Copyright (C) 2016-17 Scott Lamb <slamb@slamb.org>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# In addition, as a special exception, the copyright holders give
|
||||||
|
# permission to link the code of portions of this program with the
|
||||||
|
# OpenSSL library under certain conditions as described in each
|
||||||
|
# individual source file, and distribute linked combinations including
|
||||||
|
# the two.
|
||||||
|
#
|
||||||
|
# You must obey the GNU General Public License in all respects for all
|
||||||
|
# of the code used other than OpenSSL. If you modify file(s) with this
|
||||||
|
# exception, you may extend this exception to your version of the
|
||||||
|
# file(s), but you are not obligated to do so. If you do not wish to do
|
||||||
|
# so, delete this exception statement from your version. If you delete
|
||||||
|
# this exception statement from all source files in the program, then
|
||||||
|
# also delete it here.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
. `dirname ${BASH_SOURCE[0]}`/script-functions.sh
|
||||||
|
|
||||||
|
# Determine directory path of this script
|
||||||
|
#
|
||||||
|
initEnvironmentVars
|
||||||
|
|
||||||
|
# Process command line options
|
||||||
|
#
|
||||||
|
while getopts ":s" opt; do
|
||||||
|
case $opt in
|
||||||
|
s) NO_SERVICE=1
|
||||||
|
;;
|
||||||
|
:)
|
||||||
|
echo_fatal "Option -$OPTARG requires an argument."
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
echo_fatal "Invalid option: -$OPTARG"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
sudo_warn
|
||||||
|
|
||||||
|
sudo install -m 755 target/release/moonfire-nvr ${SERVICE_BIN}
|
||||||
|
if [ -x "${SERVICE_BIN}" ]; then
|
||||||
|
echo_info -x "Server Binary installed..."
|
||||||
|
else
|
||||||
|
echo_info -x "Server build failed to install..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -d "${LIB_DIR}" ]; then
|
||||||
|
sudo mkdir "${LIB_DIR}"
|
||||||
|
fi
|
||||||
|
if [ -d "ui-dist" ]; then
|
||||||
|
sudo mkdir -p "${LIB_DIR}/ui"
|
||||||
|
sudo cp -R ui-dist/. "${LIB_DIR}/ui/"
|
||||||
|
sudo chown -R ${NVR_USER}:${NVR_GROUP} "${LIB_DIR}/ui/"
|
||||||
|
echo_info -x "Server UI installed..."
|
||||||
|
else
|
||||||
|
echo_fatal -x "Server UI failed to build or install..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${NO_SERVICE:-0}" != 0 ]; then
|
||||||
|
echo_info -x "Not configuring systemctl service. Done"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure user was created
|
||||||
|
#
|
||||||
|
if ! userExists "${NVR_USER}"; then
|
||||||
|
echo_fatal -x "NVR_USER=${NVR_USER} was not created. Do so manually or run the setup script."
|
||||||
|
fi
|
||||||
|
|
||||||
|
pre_install_prep
|
||||||
|
|
||||||
|
|
||||||
|
# Prepare service files for socket when using priviliged port
|
||||||
|
#
|
||||||
|
SOCKET_SERVICE_PATH="/etc/systemd/system/${SERVICE_NAME}.socket"
|
||||||
|
if [ $NVR_PORT -lt 1024 ]; then
|
||||||
|
echo_fatal -x "NVR_PORT ($NVR_PORT) < 1024: priviliged ports not supported"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prepare service files for moonfire
|
||||||
|
#
|
||||||
|
SERVICE_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
|
||||||
|
sudo sh -c "cat > ${SERVICE_PATH}" <<NVR_EOF
|
||||||
|
[Unit]
|
||||||
|
Description=${SERVICE_DESC}
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=${SERVICE_BIN} run \\
|
||||||
|
--db-dir=${DB_DIR} \\
|
||||||
|
--ui-dir=${LIB_DIR}/ui \\
|
||||||
|
--http-addr=0.0.0.0:${NVR_PORT}
|
||||||
|
Environment=TZ=:/etc/localtime
|
||||||
|
Environment=MOONFIRE_FORMAT=google-systemd
|
||||||
|
Environment=MOONFIRE_LOG=info
|
||||||
|
Environment=RUST_BACKTRACE=1
|
||||||
|
Type=simple
|
||||||
|
User=${NVR_USER}
|
||||||
|
Nice=-20
|
||||||
|
Restart=on-abnormal
|
||||||
|
CPUAccounting=true
|
||||||
|
MemoryAccounting=true
|
||||||
|
BlockIOAccounting=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
NVR_EOF
|
||||||
|
|
||||||
|
# Configure and start service
|
||||||
|
#
|
||||||
|
if [ -f "${SERVICE_PATH}" ]; then
|
||||||
|
echo_info -x 'Configuring system daemon...'
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable ${SERVICE_NAME}
|
||||||
|
sudo systemctl restart ${SERVICE_NAME}
|
||||||
|
echo_info -x 'Getting system daemon status...'
|
||||||
|
sudo systemctl status ${SERVICE_NAME}
|
||||||
|
fi
|
368
scripts/script-functions.sh
Executable file
368
scripts/script-functions.sh
Executable file
@ -0,0 +1,368 @@
|
|||||||
|
#
|
||||||
|
# This file is part of Moonfire NVR, a security camera network video recorder.
|
||||||
|
# Copyright (C) 2016-2017 Scott Lamb <slamb@slamb.org>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# In addition, as a special exception, the copyright holders give
|
||||||
|
# permission to link the code of portions of this program with the
|
||||||
|
# OpenSSL library under certain conditions as described in each
|
||||||
|
# individual source file, and distribute linked combinations including
|
||||||
|
# the two.
|
||||||
|
#
|
||||||
|
# You must obey the GNU General Public License in all respects for all
|
||||||
|
# of the code used other than OpenSSL. If you modify file(s) with this
|
||||||
|
# exception, you may extend this exception to your version of the
|
||||||
|
# file(s), but you are not obligated to do so. If you do not wish to do
|
||||||
|
# so, delete this exception statement from your version. If you delete
|
||||||
|
# this exception statement from all source files in the program, then
|
||||||
|
# also delete it here.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
if [ -z "$BASH_VERSION" ]; then
|
||||||
|
echo "Script must run using bash (/bin/bash), not dash (/bin/sh)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Useful constants
|
||||||
|
#
|
||||||
|
NODE_MIN_VERSION="8"
|
||||||
|
YARN_MIN_VERSION="1.0"
|
||||||
|
CARGO_MIN_VERSION="0.2"
|
||||||
|
RUSTC_MIN_VERSION="1.17"
|
||||||
|
FFMPEG_MIN_VERSION="55.1.101"
|
||||||
|
FFMPEG_RELEASE_VERSION="3.4"
|
||||||
|
|
||||||
|
normalizeDirPath()
|
||||||
|
{
|
||||||
|
echo "$( cd -P "$1" && pwd )"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvePath()
|
||||||
|
{
|
||||||
|
local d="$1"
|
||||||
|
while [ -h "$d" ]; do # resolve $d until file no longer a symlink
|
||||||
|
DIR="$( cd -P "$( dirname "$d" )" && pwd )"
|
||||||
|
d="$(readlink "$d")"
|
||||||
|
# if $d was rel symlink, resolve relative to path of symlink
|
||||||
|
[[ "$d" != /* ]] && ="$DIR/$d"
|
||||||
|
done
|
||||||
|
echo "$d"
|
||||||
|
}
|
||||||
|
|
||||||
|
functionsInit()
|
||||||
|
{
|
||||||
|
local p="$(resolvePath "${BASH_SOURCE[0]}")"
|
||||||
|
MOONFIRE_DIR="$(normalizeDirPath "`dirname "${p}"`/..")"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_lines()
|
||||||
|
{
|
||||||
|
LINES_READ=()
|
||||||
|
while read -r line; do
|
||||||
|
LINES_READ+=("$line")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
catPrefix()
|
||||||
|
{
|
||||||
|
sed -e "s/^/$2/" < "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir_moonfire()
|
||||||
|
{
|
||||||
|
sudo -u ${NVR_USER} -H mkdir "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_multi()
|
||||||
|
{
|
||||||
|
local prefix=''
|
||||||
|
local plus=''
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
# Define a prefix for each line
|
||||||
|
-p) shift; prefix="$1"; shift ;;
|
||||||
|
# Provide extra empty line at end
|
||||||
|
-x) shift; plus=1 ;;
|
||||||
|
# Insert contents of LINES_READ here
|
||||||
|
# Only works as leading option
|
||||||
|
-L) shift; set -- "${LINES_READ[@]}" "$@" ;;
|
||||||
|
# Stop processing options
|
||||||
|
-) shift; break ;;
|
||||||
|
# Non option break out
|
||||||
|
*) break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
local A=("$@")
|
||||||
|
for l in "${A[@]/#/$prefix}"; do
|
||||||
|
echo "$l"
|
||||||
|
done
|
||||||
|
[ -n "$plus" ] && echo
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_stderr()
|
||||||
|
{
|
||||||
|
echo_multi "$@" 1>&2
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_info()
|
||||||
|
{
|
||||||
|
echo_multi -x -p '>>> ' "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
echo_warn()
|
||||||
|
{
|
||||||
|
echo_multi -p 'WARNING: ' "$@" 1>&2
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_error()
|
||||||
|
{
|
||||||
|
echo_multi -p 'ERROR: ' "$@" 1>&2
|
||||||
|
}
|
||||||
|
|
||||||
|
echo_fatal()
|
||||||
|
{
|
||||||
|
echo_error "$@"
|
||||||
|
exit 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Read possible user config and then compute all derived environment
|
||||||
|
# variables used by the script(s).
|
||||||
|
#
|
||||||
|
initEnvironmentVars()
|
||||||
|
{
|
||||||
|
test -z "${MOONFIRE_DIR}" && functionsInit
|
||||||
|
if [ -r "${MOONFIRE_DIR}/prep.config" ]; then
|
||||||
|
. "${MOONFIRE_DIR}/prep.config"
|
||||||
|
fi
|
||||||
|
SRC_DIR="$(resolvePath "$MOONFIRE_DIR/src")"
|
||||||
|
NVR_USER="${NVR_USER:-moonfire-nvr}"
|
||||||
|
NVR_GROUP="${NVR_GROUP:-$NVR_USER}"
|
||||||
|
NVR_PORT="${NVR_PORT:-8080}"
|
||||||
|
NVR_HOME_BASE="${NVR_HOME_BASE:-/var/lib}"
|
||||||
|
NVR_HOME="${NVR_HOME_BASE}/${NVR_USER}"
|
||||||
|
DB_NAME="${DB_NAME:-db}"
|
||||||
|
DB_DIR="${DB_DIR:-$NVR_HOME/${DB_NAME}}"
|
||||||
|
SAMPLE_FILE_DIR="${SAMPLE_FILE_DIR:-sample}"
|
||||||
|
SAMPLE_MEDIA_DIR="${SAMPLE_MEDIA_DIR:-$NVR_HOME}"
|
||||||
|
SERVICE_NAME="${SERVICE_NAME:-moonfire-nvr}"
|
||||||
|
SERVICE_DESC="${SERVICE_DESC:-Moonfire NVR}"
|
||||||
|
SERVICE_BIN="${SERVICE_BIN:-/usr/local/bin/moonfire-nvr}"
|
||||||
|
LIB_DIR="${UI_DIR:-/usr/local/lib/moonfire-nvr}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create file with confguration variables that are user changeable.
|
||||||
|
# Not all variables are included here, only the likely ones to be
|
||||||
|
# modified.
|
||||||
|
# If the file already exists, it will not be modified
|
||||||
|
#
|
||||||
|
makePrepConfig()
|
||||||
|
{
|
||||||
|
test -z "${MOONFIRE_DIR}" && functionsInit
|
||||||
|
if [ ! -f "${MOONFIRE_DIR}/prep.config" ]; then
|
||||||
|
cat >"${MOONFIRE_DIR}/prep.config" <<-EOF_CONFIG
|
||||||
|
NVR_USER=$NVR_USER
|
||||||
|
NVR_PORT=$NVR_PORT
|
||||||
|
SAMPLE_FILE_DIR=$SAMPLE_FILE_DIR
|
||||||
|
#SAMPLE_MEDIA_DIR=/mount/media
|
||||||
|
SERVICE_NAME=$SERVICE_NAME
|
||||||
|
SERVICE_DESC="$SERVICE_DESC"
|
||||||
|
EOF_CONFIG
|
||||||
|
echo_info -x "File prep.config newly created. Inspect and change as necessary." \
|
||||||
|
"When done, re-run this setup script. Currently it contains:"
|
||||||
|
catPrefix "${MOONFIRE_DIR}/prep.config" " "
|
||||||
|
echo_info -x
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo_info -x "Setting up with variables:"
|
||||||
|
catPrefix "${MOONFIRE_DIR}/prep.config" " "
|
||||||
|
echo_info -x
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract version data from stdin, possibly grepping first against
|
||||||
|
# single argument.
|
||||||
|
#
|
||||||
|
extractVersion()
|
||||||
|
{
|
||||||
|
local pattern="$1"
|
||||||
|
|
||||||
|
if [ -n "${pattern}" ]; then
|
||||||
|
grep "$pattern" | sed -e 's/[^0-9.]*\([0-9. ]*\).*/\1/' | tr -d ' '
|
||||||
|
else
|
||||||
|
sed -e 's/[^0-9.]*\([0-9. ]*\).*/\1/' | tr -d ' '
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
getAVersion()
|
||||||
|
{
|
||||||
|
local v=`$1 $2 2>/dev/null | extractVersion`
|
||||||
|
if [ "X${v}" = "X" ]; then echo "$3"; else echo "${v}"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
getVersion()
|
||||||
|
{
|
||||||
|
getAVersion $1 --version $2
|
||||||
|
}
|
||||||
|
|
||||||
|
getClassicVersion()
|
||||||
|
{
|
||||||
|
getAVersion $1 -version $2
|
||||||
|
}
|
||||||
|
|
||||||
|
versionAtLeast()
|
||||||
|
{
|
||||||
|
v1=(${1//./ } 0 0 0); v1=("${v1[@]:0:3}")
|
||||||
|
v2=(${2//./ } 0 0 0); v2=("${v2[@]:0:3}")
|
||||||
|
|
||||||
|
for i in 0 1 2; do
|
||||||
|
if [ "${v1[$i]}" -gt "${v2[$i]}" ]; then return 0; fi
|
||||||
|
if [ "${v1[$i]}" -lt "${v2[$i]}" ]; then return 1; fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
initCargo()
|
||||||
|
{
|
||||||
|
if [ -r ~/.cargo/env ]; then
|
||||||
|
source ~/.cargo/env
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
userExists()
|
||||||
|
{
|
||||||
|
return $(id -u "$1" >/dev/null 2>&1)
|
||||||
|
}
|
||||||
|
|
||||||
|
groupExists()
|
||||||
|
{
|
||||||
|
return $(id -g "$1" >/dev/null 2>&1)
|
||||||
|
}
|
||||||
|
|
||||||
|
moonfire()
|
||||||
|
{
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
sudo systemctl start "$2"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
sudo systemctl stop "$2"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
sudo_warn()
|
||||||
|
{
|
||||||
|
echo_warn -x -p '!!!!! ' \
|
||||||
|
'------------------------------------------------------------------------------' \
|
||||||
|
'During this script you may be asked to input your root password' \
|
||||||
|
'This is for the purpose of using the sudo command and is necessary to complete' \
|
||||||
|
'the script successfully.' \
|
||||||
|
'------------------------------------------------------------------------------'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Prepare for sqlite directory and set schema into db
|
||||||
|
#
|
||||||
|
setup_db()
|
||||||
|
{
|
||||||
|
DB_NAME=${1:-db}
|
||||||
|
if [ ! -d "${DB_DIR}" ]; then
|
||||||
|
echo_info -x 'Create database directory...'
|
||||||
|
mkdir_moonfire -p "${DB_DIR}"
|
||||||
|
fi
|
||||||
|
DB_PATH="${DB_DIR}/${DB_NAME}"
|
||||||
|
if [ ! -f "${DB_PATH}" ]; then
|
||||||
|
echo_info -x 'Create database and initialize...'
|
||||||
|
sudo -u "${NVR_USER}" -H sqlite3 "${DB_PATH}" < "${SRC_DIR}/schema.sql"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure all sample directories and files owned by moonfire
|
||||||
|
#
|
||||||
|
fix_ownership()
|
||||||
|
{
|
||||||
|
sudo chown -R ${NVR_USER}.${NVR_USER} "$1"
|
||||||
|
echo_info -x "Fix ownership of files in \"$1\"..."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Make sure samples directory is ready
|
||||||
|
#
|
||||||
|
prep_sample_file_dir()
|
||||||
|
{
|
||||||
|
if [ -z "${SAMPLE_MEDIA_DIR}" ]; then
|
||||||
|
echo_fatal -x "SAMPLE_MEDIA_DIR variable not configured. Check configuration."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SAMPLE_FILE_PATH="${SAMPLE_MEDIA_DIR}/${SAMPLE_FILE_DIR}"
|
||||||
|
if [ "${SAMPLE_FILE_PATH##${NVR_HOME}}" != "${SAMPLE_FILE_PATH}" ]; then
|
||||||
|
# Under the home directory, create if not there
|
||||||
|
if [ ! -d "${SAMPLE_FILE_PATH}" ]; then
|
||||||
|
echo_info -x "Created sample file directory: \"$SAMPLE_FILE_PATH\"..."
|
||||||
|
mkdir_moonfire -p "${SAMPLE_FILE_PATH}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ ! -d "${SAMPLE_FILE_PATH}" ]; then
|
||||||
|
read_lines <<-MSG1
|
||||||
|
Samples directory $SAMPLE_FILE_PATH does not exist.
|
||||||
|
If a mounted file system, make sure /etc/fstab is properly configured,
|
||||||
|
and file system is mounted and directory created.
|
||||||
|
MSG1
|
||||||
|
echo_fatal -L
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fix_ownership "${SAMPLE_FILE_PATH}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create user and groups if not there
|
||||||
|
#
|
||||||
|
prep_moonfire_user()
|
||||||
|
{
|
||||||
|
echo_info -x "Create user/group and directories we need..."
|
||||||
|
if ! groupExists "${NVR_GROUP}"; then
|
||||||
|
sudo addgroup --quiet --system ${NVR_GROUP}
|
||||||
|
fi
|
||||||
|
if ! userExists "${NVR_USER}"; then
|
||||||
|
sudo adduser --quiet --system ${NVR_USER} \
|
||||||
|
--ingroup "${NVR_GROUP}" --home "${NVR_HOME}"
|
||||||
|
fi
|
||||||
|
if [ ! -d "${NVR_HOME}" ]; then
|
||||||
|
mkdir_moonfire "${NVR_HOME}"
|
||||||
|
fi
|
||||||
|
sudo chown ${NVR_USER}:${NVR_GROUP} "${NVR_HOME}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Correct possible timezone issues
|
||||||
|
#
|
||||||
|
fix_localtime()
|
||||||
|
{
|
||||||
|
if [ ! -L /etc/localtime ] && [ -f /etc/timezone ] &&
|
||||||
|
[ -f "/usr/share/zoneinfo/`cat /etc/timezone`" ]; then
|
||||||
|
echo_info -x "Correcting /etc/localtime setup issue..."
|
||||||
|
sudo ln -sf /usr/share/zoneinfo/`cat /etc/timezone` /etc/localtime
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pre_install_prep()
|
||||||
|
{
|
||||||
|
prep_moonfire_user
|
||||||
|
setup_db db
|
||||||
|
prep_sample_file_dir
|
||||||
|
fix_localtime
|
||||||
|
}
|
||||||
|
|
184
scripts/setup-ubuntu.sh
Executable file
184
scripts/setup-ubuntu.sh
Executable file
@ -0,0 +1,184 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# This file is part of Moonfire NVR, a security camera network video recorder.
|
||||||
|
# Copyright (C) 2016-2017 Scott Lamb <slamb@slamb.org>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# In addition, as a special exception, the copyright holders give
|
||||||
|
# permission to link the code of portions of this program with the
|
||||||
|
# OpenSSL library under certain conditions as described in each
|
||||||
|
# individual source file, and distribute linked combinations including
|
||||||
|
# the two.
|
||||||
|
#
|
||||||
|
# You must obey the GNU General Public License in all respects for all
|
||||||
|
# of the code used other than OpenSSL. If you modify file(s) with this
|
||||||
|
# exception, you may extend this exception to your version of the
|
||||||
|
# file(s), but you are not obligated to do so. If you do not wish to do
|
||||||
|
# so, delete this exception statement from your version. If you delete
|
||||||
|
# this exception statement from all source files in the program, then
|
||||||
|
# also delete it here.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
. `dirname ${BASH_SOURCE[0]}`/script-functions.sh
|
||||||
|
|
||||||
|
initEnvironmentVars
|
||||||
|
makePrepConfig
|
||||||
|
|
||||||
|
|
||||||
|
# Process command line options
|
||||||
|
#
|
||||||
|
while getopts ":f" opt; do
|
||||||
|
case $opt in
|
||||||
|
f) DONT_BUILD_FFMPEG=1
|
||||||
|
;;
|
||||||
|
:)
|
||||||
|
echo_fatal "Option -$OPTARG requires an argument."
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
echo_fatal -x "Invalid option: -$OPTARG"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
sudo_warn
|
||||||
|
|
||||||
|
# Setup all apt packages we need
|
||||||
|
#
|
||||||
|
echo_info -x 'Preparing and downloading packages we need...'
|
||||||
|
PKGS="build-essential pkg-config sqlite3"
|
||||||
|
#PKGS="$PKGS libavcodec-dev libavformat-dev libavutil-dev"
|
||||||
|
PKGS="$PKGS libncurses5-dev libncursesw5-dev"
|
||||||
|
PKGS="$PKGS libsqlite3-dev libssl-dev"
|
||||||
|
|
||||||
|
# Add yarn before NodeSource so it can all go in one update
|
||||||
|
#
|
||||||
|
yv=$(getVersion yarn "NA")
|
||||||
|
if [ ${yv} = "NA" ]; then
|
||||||
|
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg |\
|
||||||
|
sudo apt-key add -
|
||||||
|
echo "deb https://dl.yarnpkg.com/debian/ stable main" |\
|
||||||
|
sudo tee /etc/apt/sources.list.d/yarn.list
|
||||||
|
PKGS="$PKGS yarn"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for minimum node version
|
||||||
|
#
|
||||||
|
nv=$(getVersion node 0)
|
||||||
|
if ! versionAtLeast "$nv" "$NODE_MIN_VERSION"; then
|
||||||
|
# Nodesource will also make sure we have apt-transport-https
|
||||||
|
# and will run apt-get-update when done
|
||||||
|
#
|
||||||
|
curl -sL https://deb.nodesource.com/setup_${NODE_MIN_VERSION}.x |
|
||||||
|
sudo -E bash -
|
||||||
|
PKGS="$PKGS nodejs"
|
||||||
|
DO_UPDATE=0
|
||||||
|
else
|
||||||
|
PKGS="$PKGS apt-transport-https"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run apt-get update if still necessary
|
||||||
|
#
|
||||||
|
if [ ${DO_UPDATE:-1} ]; then sudo apt-get update -y; fi
|
||||||
|
|
||||||
|
# Install necessary pakackes
|
||||||
|
#
|
||||||
|
sudo apt-get install -y $PKGS
|
||||||
|
sudo apt-get autoremove -y
|
||||||
|
echo_info -x
|
||||||
|
|
||||||
|
# Check for ffmpeg and install by building if necessary
|
||||||
|
# This needs to be done before building moonfire so it can
|
||||||
|
# find the right versions of the libraries.
|
||||||
|
#
|
||||||
|
ffv=`ffmpeg -version 2>/dev/null | extractVersion libavutil`
|
||||||
|
ffv=${ffv:-0}
|
||||||
|
if ! versionAtLeast "$ffv" "$FFMPEG_MIN_VERSION"; then
|
||||||
|
if [ "${DONT_BUILD_FFMPEG:-0}" -ne 0 ]; then
|
||||||
|
echo_warn -x "ffmpeg version (${ffv}) installed is too old for moonfire." \
|
||||||
|
"Suggest you manually install at least version $FFMPEG_MIN_VERSION of libavutil." \
|
||||||
|
"ffmpeg versions 2.x and 3.x all should work."
|
||||||
|
else
|
||||||
|
OLDDIR=`pwd`
|
||||||
|
cd ..
|
||||||
|
if [ -d FFmpeg ]; then
|
||||||
|
echo_info -x "Removing older FFmpeg directory..."
|
||||||
|
rm -fr FFmpeg
|
||||||
|
fi
|
||||||
|
echo_info -x "Fetching FFmpeg source..."
|
||||||
|
git clone --depth 1 -b "release/${FFMPEG_RELEASE_VERSION}" https://github.com/FFmpeg/FFmpeg.git
|
||||||
|
cd FFmpeg
|
||||||
|
pt=`uname -p 2>& /dev/null`
|
||||||
|
if [ -z "${pt##*86*}" ]; then
|
||||||
|
sudo apt-get install -y yasm
|
||||||
|
fi
|
||||||
|
./configure --enable-shared
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
sudo ldconfig
|
||||||
|
cd "$OLDDIR"
|
||||||
|
OLDDIR=
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo_info -x "FFmpeg is already usable..."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If cargo appears installed, initialize for using it so rustc can be found
|
||||||
|
#
|
||||||
|
initCargo
|
||||||
|
|
||||||
|
# Make sure we have rust and cargo
|
||||||
|
rv=$(getVersion rustc 0.0)
|
||||||
|
if ! versionAtLeast "$rv" "$RUSTC_MIN_VERSION"; then
|
||||||
|
echo_info -x "Installing latest rust and cargo..."
|
||||||
|
curl https://sh.rustup.rs -sSf | sh -s - -y
|
||||||
|
initCargo
|
||||||
|
fi
|
||||||
|
|
||||||
|
cv=$(getVersion cargo "NA")
|
||||||
|
if [ ${cv} = "NA" ]; then
|
||||||
|
echo_fatal -x "Cargo is not (properly) installed, but rust is." \
|
||||||
|
"Suggest you install the latest rustup, or manually install cargo."
|
||||||
|
"Install using: curl https://sh.rustup.rs -sSf | sh -s -y"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Now make sure we have dev environment and tools for the UI portion
|
||||||
|
#
|
||||||
|
echo_info -x "Installing all dependencies with yarn..."
|
||||||
|
yarn install
|
||||||
|
echo_info -x
|
||||||
|
|
||||||
|
finish()
|
||||||
|
{
|
||||||
|
if [ -z "${OLDDIR}" ]; then
|
||||||
|
cd "${OLDDIR}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap finish EXIT
|
||||||
|
|
||||||
|
# Rest of prep
|
||||||
|
#
|
||||||
|
pre_install_prep
|
||||||
|
|
||||||
|
|
||||||
|
read_lines <<-'INSTRUCTIONS'
|
||||||
|
Unless there are errors above, everything you need should have been installed
|
||||||
|
and you are now ready to build, install, and then use moonfire.
|
||||||
|
|
||||||
|
Build by executing the script: scripts/build.sh
|
||||||
|
Install by executing the script: scripts/install.sh (run automatically by build
|
||||||
|
step).
|
||||||
|
INSTRUCTIONS
|
||||||
|
echo_info -x -p ' ' -L
|
||||||
|
|
37
settings-nvr.js
Normal file
37
settings-nvr.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// vim: set et ts=2 sw=2:
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module must export a map, but can use a function with no arguments
|
||||||
|
* that returns a map, or a function that receives the "env" and "args"
|
||||||
|
* values from webpack.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
module.exports.settings = {
|
||||||
|
// Project related: use ./ in front of project root relative files!
|
||||||
|
app_src_dir: './ui-src',
|
||||||
|
dist_dir: './ui-dist',
|
||||||
|
|
||||||
|
// App related
|
||||||
|
app_title: 'Moonfire NVR',
|
||||||
|
|
||||||
|
// Where is the server to be found
|
||||||
|
moonfire: {
|
||||||
|
server: 'localhost',
|
||||||
|
port: 8080,
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In settings override file you can add sections like below on this level.
|
||||||
|
* After processing, anything defined in mode.production or mode.development,
|
||||||
|
* as appropriate based on --mode argument to webpack, will be merged
|
||||||
|
* into the top level of this settings module. This allows you to add to, or
|
||||||
|
* override anything listed above.
|
||||||
|
*
|
||||||
|
* webpack_mode: {
|
||||||
|
* production: {},
|
||||||
|
* development: {},
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
};
|
@ -55,11 +55,15 @@ Options:
|
|||||||
--db-dir=DIR Set the directory holding the SQLite3 index database.
|
--db-dir=DIR Set the directory holding the SQLite3 index database.
|
||||||
This is typically on a flash device.
|
This is typically on a flash device.
|
||||||
[default: /var/lib/moonfire-nvr/db]
|
[default: /var/lib/moonfire-nvr/db]
|
||||||
--ui-dir=DIR Set the directory with the user interface files (.html, .js, etc).
|
--ui-dir=DIR Set the directory with the user interface files
|
||||||
|
(.html, .js, etc).
|
||||||
[default: /usr/local/lib/moonfire-nvr/ui]
|
[default: /usr/local/lib/moonfire-nvr/ui]
|
||||||
--http-addr=ADDR Set the bind address for the unencrypted HTTP server.
|
--http-addr=ADDR Set the bind address for the unencrypted HTTP server.
|
||||||
[default: 0.0.0.0:8080]
|
[default: 0.0.0.0:8080]
|
||||||
--read-only Forces read-only mode / disables recording.
|
--read-only Forces read-only mode / disables recording.
|
||||||
|
--allow-origin=ORIGIN If present, adds a Access-Control-Allow-Origin:
|
||||||
|
header to HTTP responses. This may be useful for
|
||||||
|
Javascript development.
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -68,6 +72,7 @@ struct Args {
|
|||||||
flag_http_addr: String,
|
flag_http_addr: String,
|
||||||
flag_ui_dir: String,
|
flag_ui_dir: String,
|
||||||
flag_read_only: bool,
|
flag_read_only: bool,
|
||||||
|
flag_allow_origin: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_shutdown_future(h: &reactor::Handle) -> Box<Future<Item = (), Error = ()>> {
|
fn setup_shutdown_future(h: &reactor::Handle) -> Box<Future<Item = (), Error = ()>> {
|
||||||
@ -111,7 +116,8 @@ pub fn run() -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
info!("Directories are opened.");
|
info!("Directories are opened.");
|
||||||
|
|
||||||
let s = web::Service::new(db.clone(), Some(&args.flag_ui_dir), resolve_zone())?;
|
let s = web::Service::new(db.clone(), Some(&args.flag_ui_dir), args.flag_allow_origin,
|
||||||
|
resolve_zone())?;
|
||||||
|
|
||||||
// Start a streamer for each stream.
|
// Start a streamer for each stream.
|
||||||
let shutdown_streamers = Arc::new(AtomicBool::new(false));
|
let shutdown_streamers = Arc::new(AtomicBool::new(false));
|
||||||
|
24
src/web.rs
24
src/web.rs
@ -40,7 +40,7 @@ use futures::{future, stream};
|
|||||||
use futures_cpupool;
|
use futures_cpupool;
|
||||||
use json;
|
use json;
|
||||||
use http_serve;
|
use http_serve;
|
||||||
use hyper::header;
|
use hyper::header::{self, Header};
|
||||||
use hyper::server::{self, Request, Response};
|
use hyper::server::{self, Request, Response};
|
||||||
use mime;
|
use mime;
|
||||||
use mp4;
|
use mp4;
|
||||||
@ -192,6 +192,7 @@ struct ServiceInner {
|
|||||||
db: Arc<db::Database>,
|
db: Arc<db::Database>,
|
||||||
dirs_by_stream_id: Arc<FnvHashMap<i32, Arc<SampleFileDir>>>,
|
dirs_by_stream_id: Arc<FnvHashMap<i32, Arc<SampleFileDir>>>,
|
||||||
ui_files: HashMap<String, UiFile>,
|
ui_files: HashMap<String, UiFile>,
|
||||||
|
allow_origin: Option<header::AccessControlAllowOrigin>,
|
||||||
pool: futures_cpupool::CpuPool,
|
pool: futures_cpupool::CpuPool,
|
||||||
time_zone_name: String,
|
time_zone_name: String,
|
||||||
}
|
}
|
||||||
@ -416,7 +417,8 @@ impl ServiceInner {
|
|||||||
pub struct Service(Arc<ServiceInner>);
|
pub struct Service(Arc<ServiceInner>);
|
||||||
|
|
||||||
impl Service {
|
impl Service {
|
||||||
pub fn new(db: Arc<db::Database>, ui_dir: Option<&str>, zone: String) -> Result<Self, Error> {
|
pub fn new(db: Arc<db::Database>, ui_dir: Option<&str>, allow_origin: Option<String>,
|
||||||
|
zone: String) -> Result<Self, Error> {
|
||||||
let mut ui_files = HashMap::new();
|
let mut ui_files = HashMap::new();
|
||||||
if let Some(d) = ui_dir {
|
if let Some(d) = ui_dir {
|
||||||
Service::fill_ui_files(d, &mut ui_files);
|
Service::fill_ui_files(d, &mut ui_files);
|
||||||
@ -438,10 +440,15 @@ impl Service {
|
|||||||
}
|
}
|
||||||
Arc::new(d)
|
Arc::new(d)
|
||||||
};
|
};
|
||||||
|
let allow_origin = match allow_origin {
|
||||||
|
None => None,
|
||||||
|
Some(o) => Some(header::AccessControlAllowOrigin::parse_header(&header::Raw::from(o))?),
|
||||||
|
};
|
||||||
Ok(Service(Arc::new(ServiceInner {
|
Ok(Service(Arc::new(ServiceInner {
|
||||||
db,
|
db,
|
||||||
dirs_by_stream_id,
|
dirs_by_stream_id,
|
||||||
ui_files,
|
ui_files,
|
||||||
|
allow_origin,
|
||||||
pool: futures_cpupool::Builder::new().pool_size(1).name_prefix("static").create(),
|
pool: futures_cpupool::Builder::new().pool_size(1).name_prefix("static").create(),
|
||||||
time_zone_name: zone,
|
time_zone_name: zone,
|
||||||
})))
|
})))
|
||||||
@ -466,8 +473,11 @@ impl Service {
|
|||||||
};
|
};
|
||||||
let (p, mime) = match e.file_name().to_str() {
|
let (p, mime) = match e.file_name().to_str() {
|
||||||
Some(n) if n == "index.html" => ("/".to_owned(), mime::TEXT_HTML),
|
Some(n) if n == "index.html" => ("/".to_owned(), mime::TEXT_HTML),
|
||||||
Some(n) if n.ends_with(".js") => (format!("/{}", n), mime::TEXT_JAVASCRIPT),
|
|
||||||
Some(n) if n.ends_with(".html") => (format!("/{}", n), mime::TEXT_HTML),
|
Some(n) if n.ends_with(".html") => (format!("/{}", n), mime::TEXT_HTML),
|
||||||
|
Some(n) if n.ends_with(".ico") => (format!("/{}", n),
|
||||||
|
"image/vnd.microsoft.icon".parse().unwrap()),
|
||||||
|
Some(n) if n.ends_with(".js") => (format!("/{}", n), mime::TEXT_JAVASCRIPT),
|
||||||
|
Some(n) if n.ends_with(".map") => (format!("/{}", n), mime::TEXT_JAVASCRIPT),
|
||||||
Some(n) if n.ends_with(".png") => (format!("/{}", n), mime::IMAGE_PNG),
|
Some(n) if n.ends_with(".png") => (format!("/{}", n), mime::IMAGE_PNG),
|
||||||
Some(n) => {
|
Some(n) => {
|
||||||
warn!("UI directory file {:?} has unknown extension; skipping", n);
|
warn!("UI directory file {:?} has unknown extension; skipping", n);
|
||||||
@ -509,6 +519,11 @@ impl server::Service for Service {
|
|||||||
Path::NotFound => self.0.not_found(),
|
Path::NotFound => self.0.not_found(),
|
||||||
Path::Static => self.0.static_file(&req),
|
Path::Static => self.0.static_file(&req),
|
||||||
};
|
};
|
||||||
|
let res = if let Some(ref o) = self.0.allow_origin {
|
||||||
|
res.map(|resp| resp.with_header(o.clone()))
|
||||||
|
} else {
|
||||||
|
res
|
||||||
|
};
|
||||||
future::result(res.map_err(|e| {
|
future::result(res.map_err(|e| {
|
||||||
error!("error: {}", e);
|
error!("error: {}", e);
|
||||||
hyper::Error::Incomplete
|
hyper::Error::Incomplete
|
||||||
@ -570,7 +585,8 @@ mod bench {
|
|||||||
let (tx, rx) = ::std::sync::mpsc::channel();
|
let (tx, rx) = ::std::sync::mpsc::channel();
|
||||||
::std::thread::spawn(move || {
|
::std::thread::spawn(move || {
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
let service = super::Service::new(db.db.clone(), None, "".to_owned()).unwrap();
|
let service = super::Service::new(db.db.clone(), None, None,
|
||||||
|
"".to_owned()).unwrap();
|
||||||
let server = hyper::server::Http::new()
|
let server = hyper::server::Http::new()
|
||||||
.bind(&addr, move || Ok(service.clone()))
|
.bind(&addr, move || Ok(service.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
BIN
ui-src/favicon.ico
Normal file
BIN
ui-src/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
@ -2,16 +2,14 @@
|
|||||||
<!-- vim: set et: -->
|
<!-- vim: set et: -->
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>moonfire ui</title>
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
||||||
<script src="bundle.js"></script>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
#nav {
|
#nav {
|
||||||
position: fixed;
|
float: left;
|
||||||
left: 0px;
|
|
||||||
top: 0px;
|
|
||||||
width: 17em;
|
width: 17em;
|
||||||
}
|
}
|
||||||
.ui-datepicker { width: 100%; }
|
#nav .ui-datepicker { width: 100%; }
|
||||||
|
|
||||||
#videos {
|
#videos {
|
||||||
margin-left: 18em;
|
margin-left: 18em;
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
// TODO: style: no globals? string literals? line length? fn comments?
|
// TODO: style: no globals? string literals? line length? fn comments?
|
||||||
// TODO: live updating.
|
// TODO: live updating.
|
||||||
|
|
||||||
|
import './favicon.ico';
|
||||||
|
|
||||||
import 'jquery-ui/themes/base/button.css';
|
import 'jquery-ui/themes/base/button.css';
|
||||||
import 'jquery-ui/themes/base/core.css';
|
import 'jquery-ui/themes/base/core.css';
|
||||||
import 'jquery-ui/themes/base/datepicker.css';
|
import 'jquery-ui/themes/base/datepicker.css';
|
||||||
@ -56,13 +58,38 @@ function req(url) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Produces a human-readable but unambiguous format of the given timestamp:
|
/**
|
||||||
// ISO-8601 plus an extra colon component after the seconds indicating the
|
* Format timestamp using a format string.
|
||||||
// fractional time in 90,000ths of a second.
|
*
|
||||||
function formatTime(ts90k) {
|
* The timestamp to be formatted is expected to be in units of 90,000 to a
|
||||||
const m = moment.tz(ts90k / 90, zone);
|
* second (90k format).
|
||||||
const frac = ts90k % 90000;
|
*
|
||||||
return m.format('YYYY-MM-DDTHH:mm:ss:' + String(100000 + frac).substr(1) + 'Z');
|
* The format string should comply with what is accepted by moment.format,
|
||||||
|
* with one addition. A format pattern of FFFFF (5 Fs) can be used. This
|
||||||
|
* format pattern will be replaced with the fractional second part of the
|
||||||
|
* timestamp, still in 90k units. Thus if the timestamp was 89900 (which is
|
||||||
|
* almost a full second; 0.99888 seconds decimal), the output would be
|
||||||
|
* 89900, and NOT 0.99888. Only a pattern of five Fs is recognized and it
|
||||||
|
* will produce exactly a five position output! You cannot vary the number
|
||||||
|
* of Fs to produce less.
|
||||||
|
*
|
||||||
|
* The default format string was chosen to produce results identical to
|
||||||
|
* a previous version of this code that was hard-coded to produce that output.
|
||||||
|
*
|
||||||
|
* @param {Number} ts90k Timestamp in 90k units
|
||||||
|
* @param {String} format moment.format plus FFFFF pattern supported
|
||||||
|
* @return {String} Formatted timestamp
|
||||||
|
*/
|
||||||
|
function formatTime(ts90k, format = 'YYYY-MM-DDTHH:mm:ss:FFFFFZ') {
|
||||||
|
const ms = ts90k / 90.0;
|
||||||
|
const fracFmt = 'FFFFF';
|
||||||
|
let fracLoc = format.indexOf(fracFmt);
|
||||||
|
if (fracLoc != -1) {
|
||||||
|
const frac = ts90k % 90000;
|
||||||
|
format = format.substr(0, fracLoc) + String(100000 + frac).substr(1) +
|
||||||
|
format.substr(fracLoc + fracFmt.length);
|
||||||
|
}
|
||||||
|
return moment.tz(ms, zone).format(format);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSelectVideo(camera, streamType, range, recording) {
|
function onSelectVideo(camera, streamType, range, recording) {
|
||||||
@ -111,7 +138,12 @@ function onSelectVideo(camera, streamType, range, recording) {
|
|||||||
dialog.dialog({
|
dialog.dialog({
|
||||||
title: camera.shortName + " " + streamType + ", " + formattedStart + " to " + formattedEnd,
|
title: camera.shortName + " " + streamType + ", " + formattedStart + " to " + formattedEnd,
|
||||||
width: recording.videoSampleEntryWidth / 4,
|
width: recording.videoSampleEntryWidth / 4,
|
||||||
close: function() { dialog.remove(); },
|
close: () => {
|
||||||
|
const videoDOMElement = video[0];
|
||||||
|
videoDOMElement.pause();
|
||||||
|
videoDOMElement.src = ''; // Remove current source to stop loading
|
||||||
|
dialog.remove();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
video.attr("src", url);
|
video.attr("src", url);
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
const MinifyPlugin = require("babel-minify-webpack-plugin");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: './ui-src/index.js',
|
|
||||||
output: {
|
|
||||||
filename: 'bundle.js',
|
|
||||||
path: path.resolve(__dirname, 'ui-dist')
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
loaders: [
|
|
||||||
{ test: /\.png$/, loader: "file-loader" },
|
|
||||||
{ test: /\.css$/, loader: "style-loader!css-loader" },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
|
||||||
/node_modules\/moment\/moment\.js$/,
|
|
||||||
'./min/moment.min.js'),
|
|
||||||
new webpack.IgnorePlugin(/\.\/locale$/),
|
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
|
||||||
/node_modules\/moment-timezone\/index\.js$/,
|
|
||||||
'./builds/moment-timezone-with-data-2012-2022.min.js'),
|
|
||||||
new MinifyPlugin({}, {})
|
|
||||||
]
|
|
||||||
};
|
|
51
webpack/NVRSettings.js
Normal file
51
webpack/NVRSettings.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// vim: set et ts=2 sw=2:
|
||||||
|
//
|
||||||
|
const path = require('path');
|
||||||
|
const Settings = require('./parts/Settings');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports a sub-class of Settings specifically for the Moonfire NVR project.
|
||||||
|
*
|
||||||
|
* Gives us a simpler constructor that encapsulates the names of the expected
|
||||||
|
* settings files.
|
||||||
|
*
|
||||||
|
* Provide some convenience member variables:
|
||||||
|
* config {object} Map of the original settings configuration
|
||||||
|
* values {object} The values map of the settings that were configured
|
||||||
|
*
|
||||||
|
* @type {NVRSettings}
|
||||||
|
*/
|
||||||
|
module.exports = class NVRSettings extends Settings {
|
||||||
|
/**
|
||||||
|
* Construct an NVRSettings object.
|
||||||
|
*
|
||||||
|
* This object will be a subclass of Settings, with some extra functionality.
|
||||||
|
*
|
||||||
|
* Initializes the super Settings object with the proper project root
|
||||||
|
* and named settings files.
|
||||||
|
*
|
||||||
|
* @param {object} env "env" object passed to webpack config function
|
||||||
|
* @param {object} args "args" object passed to webpack config function
|
||||||
|
* @param {String} projectRoot Project root, defaults to '.' which is
|
||||||
|
* usually the directory from which you run
|
||||||
|
* npm or yarn.
|
||||||
|
*/
|
||||||
|
constructor(env, args, projectRoot = './') {
|
||||||
|
super({
|
||||||
|
projectRoot: path.resolve(projectRoot),
|
||||||
|
primaryFile: 'settings-nvr.js',
|
||||||
|
secondaryFile: 'settings-nvr-local.js',
|
||||||
|
env: env,
|
||||||
|
args: args,
|
||||||
|
});
|
||||||
|
const config = this.settings_config;
|
||||||
|
// Add some absolute paths that might be relevant
|
||||||
|
this.settings = Object.assign(this.settings, {
|
||||||
|
_paths: {
|
||||||
|
project_root: config.projectRoot,
|
||||||
|
app_src_dir: path.join(config.projectRoot, this.settings.app_src_dir),
|
||||||
|
dist_dir: path.join(config.projectRoot, this.settings.dist_dir),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
62
webpack/base.config.js
Normal file
62
webpack/base.config.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// vim: set et ts=2 sw=2:
|
||||||
|
//
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const NVRSettings = require('./NVRSettings');
|
||||||
|
|
||||||
|
module.exports = (env, args) => {
|
||||||
|
const nvrSettings = new NVRSettings(env, args).settings;
|
||||||
|
|
||||||
|
return {
|
||||||
|
entry: {
|
||||||
|
nvr: path.join(nvrSettings._paths.app_src_dir, 'index.js'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: '[name].bundle.js',
|
||||||
|
path: nvrSettings._paths.dist_dir,
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [{
|
||||||
|
test: /\.js$/,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
query: {
|
||||||
|
'presets': ['env'],
|
||||||
|
},
|
||||||
|
exclude: /(node_modules|bower_components)/,
|
||||||
|
include: [ './ui-src'],
|
||||||
|
}, {
|
||||||
|
test: /\.png$/,
|
||||||
|
use: ['file-loader'],
|
||||||
|
}, {
|
||||||
|
test: /\.ico$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: '[name].[ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
// Load css and then in-line in head
|
||||||
|
test: /\.css$/,
|
||||||
|
loader: 'style-loader!css-loader',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.IgnorePlugin(/\.\/locale$/),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
title: nvrSettings.app_title,
|
||||||
|
template: path.join(nvrSettings._paths.app_src_dir, 'index.html'),
|
||||||
|
}),
|
||||||
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
|
/node_modules\/moment\/moment\.js$/,
|
||||||
|
'./min/moment.min.js'),
|
||||||
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
|
/node_modules\/moment-timezone\/index\.js$/,
|
||||||
|
'./builds/moment-timezone-with-data-2012-2022.min.js'),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
35
webpack/dev.config.js
Normal file
35
webpack/dev.config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// vim: set et ts=2 sw=2:
|
||||||
|
//
|
||||||
|
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const NVRSettings = require('./NVRSettings');
|
||||||
|
const baseConfig = require('./base.config.js');
|
||||||
|
|
||||||
|
module.exports = (env, args) => {
|
||||||
|
const settingsObject = new NVRSettings(env, args);
|
||||||
|
const nvrSettings = settingsObject.settings;
|
||||||
|
|
||||||
|
return settingsObject.webpackMerge(baseConfig, {
|
||||||
|
stats: {
|
||||||
|
warnings: true,
|
||||||
|
},
|
||||||
|
devtool: 'inline-source-map',
|
||||||
|
devServer: {
|
||||||
|
contentBase: nvrSettings.app_src_dir,
|
||||||
|
historyApiFallback: true,
|
||||||
|
inline: true,
|
||||||
|
port: 3000,
|
||||||
|
hot: true,
|
||||||
|
clientLogLevel: 'info',
|
||||||
|
proxy: {
|
||||||
|
'/api': `http://${nvrSettings.moonfire.server}:${nvrSettings.moonfire.port}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': JSON.stringify('development'),
|
||||||
|
}),
|
||||||
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
200
webpack/parts/Settings.js
Normal file
200
webpack/parts/Settings.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// vim: set et ts=2 sw=2:
|
||||||
|
//
|
||||||
|
const path = require('path');
|
||||||
|
const merge = require('webpack-merge');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to require a file and catch errors so we can
|
||||||
|
* distinguish between failure to find the module and errors in the
|
||||||
|
* module.
|
||||||
|
*
|
||||||
|
* When a require results in errors (as opposed to the file not being
|
||||||
|
* found), we throw an exception.
|
||||||
|
*
|
||||||
|
* If the module that is require-d is a function, it will be executed,
|
||||||
|
* passing the "env" and "args" parameters from the settingsConfig to it.
|
||||||
|
* The function should return a map.
|
||||||
|
*
|
||||||
|
* @param {String} path Path to be passed to require()
|
||||||
|
* @param {object} settingsConfig Settings passed to new Settings()
|
||||||
|
* @param {Boolean} optional True file not to exist
|
||||||
|
* @return {object} The module, or {} if not found (optional)
|
||||||
|
*/
|
||||||
|
function requireHelper(path, settingsConfig, optional) {
|
||||||
|
let module = {};
|
||||||
|
try {
|
||||||
|
require.resolve(path); // Throws if not found
|
||||||
|
try {
|
||||||
|
module = require(path);
|
||||||
|
if (typeof(module) === 'function') {
|
||||||
|
module = module(settingsConfig.env, settingsConfig.args);
|
||||||
|
}
|
||||||
|
// Get owned properties only: now a literal map
|
||||||
|
module = Object.assign({}, require(path).settings);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Settings file (' + path + ') has errors.');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!optional) {
|
||||||
|
throw new Error('Settings file (' + path + ') not found.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const args = settingsConfig.args;
|
||||||
|
const webpackMode = (args ? args.mode : null) || 'none';
|
||||||
|
const modes = module.webpack_mode || {};
|
||||||
|
delete module.webpack_mode; // Not modifying original module. We have a copy!
|
||||||
|
if (webpackMode && modes) {
|
||||||
|
module = merge(module, modes[webpackMode]);
|
||||||
|
}
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General purpose settings loading class.
|
||||||
|
*
|
||||||
|
* The class first reads a specified file extracting a map object with
|
||||||
|
* settings. It then attempts to read a second file which, if successfull,
|
||||||
|
* will be merged to override values from the first.
|
||||||
|
*
|
||||||
|
* The module exported in each file must either be a map, in which case
|
||||||
|
* it is used directly, or a function with no arguments. In the latter case
|
||||||
|
* it will be called in order to obtain the map.
|
||||||
|
*
|
||||||
|
* The intended use is that the first file contains project level settings
|
||||||
|
* that are checked into a repository. The second file should be for local
|
||||||
|
* (development) overrides and should not be checked in.
|
||||||
|
*
|
||||||
|
* If the primary file is allowed optional and is not found, we still
|
||||||
|
* attempt to read the secondary, but it is never an error if that file
|
||||||
|
* does not exist.
|
||||||
|
*
|
||||||
|
* Both primary and secondary files may contain a property called webpack_mode
|
||||||
|
* that, in turn, may contain properties named "development" and
|
||||||
|
* "production". During loading, if these properties are present, the whole
|
||||||
|
* "webpack_mode" property is *NOT* delivered in the final result, but the
|
||||||
|
* sub-property corresponding to webpack's "--mode" argument is merged
|
||||||
|
* with the configuration object at the top-level. This allows either
|
||||||
|
* sub-property to override defaults in the settings.
|
||||||
|
*
|
||||||
|
* Provide some convenience member variables in the Settings object:
|
||||||
|
* settings_config {object} object with the arguments to the constructor
|
||||||
|
* settings {object} The values map of the settings that were configured
|
||||||
|
*
|
||||||
|
* In many cases a user of this class will only be intersted in the values
|
||||||
|
* component. A typical usage patterns would the be:
|
||||||
|
* <pre><code>
|
||||||
|
* const Settings = require('Settings');
|
||||||
|
* const settings = (new Settings()).values;
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* This does make the "config" component of the Settings instance unavailable.
|
||||||
|
* That can be remedied:
|
||||||
|
* <pre><code>
|
||||||
|
* const Settings = require('Settings');
|
||||||
|
* const _settings = new Settings();
|
||||||
|
* const settings = _settings.values;
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* Now the config is available as "_settings.config".
|
||||||
|
*
|
||||||
|
* @type {NVRSettings}
|
||||||
|
*/
|
||||||
|
class Settings {
|
||||||
|
/**
|
||||||
|
* Construct the settings object by attempting to read and merge
|
||||||
|
* both files.
|
||||||
|
*
|
||||||
|
* Settings file and alternate or specified as filenames only. They
|
||||||
|
* are always looked for in the project root directory.
|
||||||
|
*
|
||||||
|
* "env", and "args" options are intended to be passed in like so:
|
||||||
|
* <pre><code>
|
||||||
|
* const Settings = require('./Settings');
|
||||||
|
*
|
||||||
|
* module.exports = (env, args) => {
|
||||||
|
* const settingsObject = new Settings({ env: env, args: args });
|
||||||
|
* const settings = settingsObject.settings;
|
||||||
|
*
|
||||||
|
* return {
|
||||||
|
* ... webpack config here, using things like
|
||||||
|
* ... settings.app_title
|
||||||
|
* };
|
||||||
|
* }
|
||||||
|
* </code></pre>
|
||||||
|
*
|
||||||
|
* The Settings object inspects "args.mode" to determine how to overload
|
||||||
|
* some settings values, and defaults to 'none' if not present.
|
||||||
|
* Alternatively, null can be passed for "env", and you could pass
|
||||||
|
* <pre>{ mode: 'development' }</pre> for args (or use 'production').
|
||||||
|
* Both values will be available later from settingsObject.settings_config
|
||||||
|
* and using the values from webpack gives full access to everything webpack
|
||||||
|
* knows.
|
||||||
|
*
|
||||||
|
* @param {Boolean} options.optional True if main file is optional
|
||||||
|
* @param {String} options.projectRoot Path to project root
|
||||||
|
* @param {String} options.primaryFile Name of main settings file
|
||||||
|
* @param {String} options.secondaryFile Name of secondary settings file
|
||||||
|
* @param {String} options.env Environment variables (from webpack)
|
||||||
|
* @param {String} options.args Arguments (from webpack)
|
||||||
|
*/
|
||||||
|
constructor({
|
||||||
|
optional = false,
|
||||||
|
projectRoot = './',
|
||||||
|
primaryFile = 'settings.js',
|
||||||
|
secondaryFile = 'settings-local.js',
|
||||||
|
env = null,
|
||||||
|
args = null,
|
||||||
|
} = {}) {
|
||||||
|
if (!projectRoot) {
|
||||||
|
throw new Error('projectRoot argument for Settings is not set.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember settings, as provided
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
|
this.settings_config = arguments[0];
|
||||||
|
|
||||||
|
// Convert settings file names into absolute paths.
|
||||||
|
const primaryPath = path.resolve(projectRoot, primaryFile);
|
||||||
|
const secondaryPath = path.resolve(projectRoot, secondaryFile);
|
||||||
|
|
||||||
|
// Check if we can resolve the primary file and if we can, require it.
|
||||||
|
const _settings =
|
||||||
|
requireHelper(primaryPath, this.settings_config, optional);
|
||||||
|
|
||||||
|
// Merge secondary override file, if it exists
|
||||||
|
this.settings = merge(_settings,
|
||||||
|
requireHelper(secondaryPath, this.settings_config, true));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take one or more webpack configurations and merge them.
|
||||||
|
*
|
||||||
|
* This uses the webpack-merge functionality, but each argument is subjected
|
||||||
|
* to some pre-processing.
|
||||||
|
* - If the argument is a string, a 'require' is performed with it first
|
||||||
|
* - If the remaining value is a function, it is expected to be like a
|
||||||
|
* webpack initialization function which gets passed "env" and "args"
|
||||||
|
* and it is called like that.
|
||||||
|
* - The remaining value is fed to webpack-merge.
|
||||||
|
*
|
||||||
|
* @param {[object]} webpackConfig1 Object representing the config
|
||||||
|
* @return {[type]} Merged configuration
|
||||||
|
*/
|
||||||
|
webpackMerge(...packs) {
|
||||||
|
const unpack = (webpackConfig) => {
|
||||||
|
if ((typeof(webpackConfig) === 'string') ||
|
||||||
|
(webpackConfig instanceof String)) {
|
||||||
|
webpackConfig = require(webpackConfig);
|
||||||
|
}
|
||||||
|
const config = this.settings_config;
|
||||||
|
if (typeof(webpackConfig) === 'function') {
|
||||||
|
return webpackConfig(config.env, config.args);
|
||||||
|
}
|
||||||
|
return webpackConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
return merge(packs.map((p) => unpack(p)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Settings;
|
40
webpack/prod.config.js
Normal file
40
webpack/prod.config.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// vim: set et ts=2 sw=2:
|
||||||
|
//
|
||||||
|
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const NVRSettings = require('./NVRSettings');
|
||||||
|
const baseConfig = require('./base.config.js');
|
||||||
|
|
||||||
|
const CleanWebpackPlugin = require('clean-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = (env, args) => {
|
||||||
|
const settingsObject = new NVRSettings(env, args);
|
||||||
|
const nvrSettings = settingsObject.settings;
|
||||||
|
|
||||||
|
return settingsObject.webpackMerge(baseConfig, {
|
||||||
|
optimization: {
|
||||||
|
splitChunks: {
|
||||||
|
cacheGroups: {
|
||||||
|
default: {
|
||||||
|
minChunks: 2,
|
||||||
|
priority: -20,
|
||||||
|
},
|
||||||
|
commons: {
|
||||||
|
test: /[\\/]node_modules[\\/]/,
|
||||||
|
name: 'vendor',
|
||||||
|
chunks: 'all',
|
||||||
|
priority: -10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||||
|
}),
|
||||||
|
new CleanWebpackPlugin([nvrSettings.dist_dir], {
|
||||||
|
root: nvrSettings._paths.project_root,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user