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:
Scott Lamb 2018-03-24 22:29:40 -07:00
commit 299c0b1802
27 changed files with 6187 additions and 827 deletions

3
.gitignore vendored
View File

@ -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

View File

@ -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
View 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
View 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
View 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.

View File

@ -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
View 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":
{
}
}

View File

@ -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
View File

@ -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
View 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
View File

@ -0,0 +1,3 @@
#!/bin/sh
#
rm -fr node_modules

135
scripts/install.sh Executable file
View 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
View 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
View 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
View 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: {},
},
*/
};

View File

@ -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));

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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;

View File

@ -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);
} }

View File

@ -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
View 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
View 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
View 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
View 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
View 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,
}),
],
});
};

4059
yarn.lock

File diff suppressed because it is too large Load Diff