From ee98bf5236ce9f09666c4317defbe7ad4297412b Mon Sep 17 00:00:00 2001 From: Scott Lamb Date: Sat, 14 Oct 2023 15:52:06 -0700 Subject: [PATCH] no more Docker! * use `termion` rather than `ncurses` to limit runtime deps * cross-compile with `cross` instead of our own dockerfiles/scripts * update instructions * update release procedure and GitHub actions to match * prep changelog for `v0.7.8` Fixes #160 Closes #265 --- .github/ISSUE_TEMPLATE/bug_report.md | 3 +- .github/workflows/ci.yml | 19 +-- .github/workflows/release.yml | 104 +++++++++++- .gitignore | 1 + CHANGELOG.md | 49 +++--- docker/Dockerfile | 75 --------- docker/build-server.bash | 37 ---- docker/build-ui.bash | 24 --- docker/deploy.bash | 32 ---- docker/dev-common.bash | 82 --------- docker/dev.bash | 122 -------------- guide/build.md | 241 +++------------------------ guide/install.md | 180 ++++++++------------ guide/schema.md | 28 ++-- guide/secure.md | 20 +-- guide/troubleshooting.md | 28 +--- release.bash | 51 ------ server/Cargo.lock | 46 +++-- server/Cargo.toml | 2 +- server/Cross.toml | 14 ++ server/build.rs | 90 +++++++--- server/src/bundled_ui.rs | 1 + server/src/main.rs | 3 +- 23 files changed, 363 insertions(+), 889 deletions(-) delete mode 100644 docker/Dockerfile delete mode 100755 docker/build-server.bash delete mode 100755 docker/build-ui.bash delete mode 100755 docker/deploy.bash delete mode 100755 docker/dev-common.bash delete mode 100755 docker/dev.bash delete mode 100755 release.bash create mode 100644 server/Cross.toml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 863a5587..524bfd39 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -24,8 +24,7 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Server (please complete the following information):** - - If using Docker: `docker ps` + `docker images` - - If building from git: `moonfire-nvr --version` + - `moonfire-nvr --version` - Attach a [log file](https://github.com/scottlamb/moonfire-nvr/blob/master/guide/troubleshooting.md#viewing-moonfire-nvrs-logs). Run with the `RUST_BACKTRACE=1` environment variable set if possible. **Camera (please complete the following information):** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce524ae9..fd1bf18a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,10 @@ name: CI on: [push, pull_request] +defaults: + run: + shell: bash + env: CARGO_TERM_COLOR: always MOONFIRE_COLOR: always @@ -37,11 +41,7 @@ jobs: restore-keys: | cargo-${{ matrix.rust }}- cargo- - - name: Install dependencies - # The retry here is to work around "Unable to connect to azure.archive.ubuntu.com" errors. - # https://github.com/actions/runner-images/issues/6894 - run: sudo apt-get --option=APT::Acquire::Retries=3 update && sudo apt-get --option=APT::Acquire::Retries=3 install libncurses-dev libsqlite3-dev pkgconf - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: node-version: 18 - name: Install Rust @@ -51,13 +51,10 @@ jobs: toolchain: ${{ matrix.rust }} override: true components: ${{ matrix.extra_components }} - - run: cd ui && npm ci - - run: cd ui && npm run build - name: Test run: | cd server - cargo test ${{ matrix.extra_args }} --all - cargo test --features=bundled-ui ${{ matrix.extra_args }} --all + cargo test --features=rusqlite/bundled ${{ matrix.extra_args }} --all continue-on-error: ${{ matrix.rust == 'nightly' }} - name: Check formatting if: matrix.rust == 'stable' @@ -70,7 +67,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} - run: cd ui && npm ci @@ -83,5 +80,5 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - run: find . -type f -print0 | xargs -0 .github/workflows/check-license.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 34d30c0e..59b43c0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,19 +1,109 @@ name: Release +defaults: + run: + shell: bash + on: push: tags: - v[0-9]+.* jobs: - create-release: + base: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: taiki-e/create-gh-release-action@v1 + - name: Checkout + uses: actions/checkout@v4 + - uses: taiki-e/install-action@v2 + with: + tool: parse-changelog + - name: Generate changelog + run: | + VERSION_MINUS_V=${GITHUB_REF_NAME/#v/} + parse-changelog CHANGELOG.md $VERSION_MINUS_V > CHANGELOG-$GITHUB_REF_NAME.md + - uses: actions/setup-node@v3 + with: + node-version: 18 + - run: cd ui && npm ci + - run: cd ui && npm run build + - run: cd ui && npm run test + # Upload the UI and changelog as *job* artifacts (not *release* artifacts), used below. + - uses: actions/upload-artifact@v3 with: - # (Optional) Path to changelog. - changelog: CHANGELOG.md + name: moonfire-nvr-ui-${{ github.ref_name }} + path: ui/build + if-no-files-found: error + - uses: actions/upload-artifact@v3 + with: + name: CHANGELOG-${{ github.ref_name }} + path: CHANGELOG-${{ github.ref_name }}.md + if-no-files-found: error + + cross: + needs: base # for bundled ui + strategy: + matrix: + include: + - target: x86_64-unknown-linux-musl + - target: aarch64-unknown-linux-musl + - target: armv7-unknown-linux-musleabihf + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Download UI + uses: actions/download-artifact@v3 + with: + name: moonfire-nvr-ui-${{ github.ref_name }} + path: ui/build + + # actions-rust-cross doesn't actually use cross for x86_64. + # Install the needed musl-tools in the host. + - name: Install musl-tools + run: sudo apt-get --option=APT::Acquire::Retries=3 update && sudo apt-get --option=APT::Acquire::Retries=3 install musl-tools + if: matrix.target == 'x86_64-unknown-linux-musl' + - name: Build + uses: houseabsolute/actions-rust-cross@v0 env: - # (Required) GitHub token for creating GitHub Releases. - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + UI_BUILD_DIR: ../ui/build + + # cross doesn't install `git` within its Docker container, so plumb + # the version through rather than try `git describe` from `build.rs`. + VERSION: ${{ github.ref_name }} + with: + working-directory: server + target: ${{ matrix.target }} + command: build + args: --release --features bundled + # Upload as a *job* artifact (not *release* artifact), used below. + - name: Upload + uses: actions/upload-artifact@v3 + with: + name: moonfire-nvr-${{ github.ref_name }}-${{ matrix.target }} + path: server/target/${{ matrix.target }}/release/moonfire-nvr + if-no-files-found: error + + release: + needs: [ base, cross ] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v3 + with: + path: artifacts + - name: ls before rearranging + run: find . -ls + - name: Rearrange + run: | + (cd artifacts/moonfire-nvr-ui-${GITHUB_REF_NAME} && zip -r ../../moonfire-nvr-ui-${GITHUB_REF_NAME}.zip .) + (cd artifacts; for i in moonfire-nvr-*/moonfire-nvr; do mv $i "../$(dirname $i)"; done) + - name: ls after rearranging + run: find . -ls + - name: Create GitHub release + uses: softprops/action-gh-release@v1 + with: + body_path: artifacts/CHANGELOG-${{ github.ref_name }}/CHANGELOG-${{ github.ref_name }}.md + files: | + moonfire-nvr-* diff --git a/.gitignore b/.gitignore index de3f7b0a..df260e25 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store *.swp +/release-* /server/target .idea diff --git a/CHANGELOG.md b/CHANGELOG.md index 064d91c2..e1039c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,22 @@ # Moonfire NVR change log Below are some highlights in each release. For a full description of all -changes, see Git history. - -Each release is tagged in Git and on the Docker repository -[`scottlamb/moonfire-nvr`](https://hub.docker.com/r/scottlamb/moonfire-nvr). +changes, see Git history. Each release is tagged in git. Backwards-incompatible database schema changes happen on on major version -upgrades, e.g. `0.6.x` -> `0.7.x`. The config file format and +upgrades, e.g. `v0.6.x` -> `v0.7.x`. The config file format and [API](ref/api.md) currently have no stability guarantees, so they may change -even on minor releases, e.g. `0.7.5` -> `0.7.6`. +even on minor releases, e.g. `v0.7.5` -> `v0.7.6`. + +## v0.7.8 (2023-10-18) + +* release as self-contained Linux binaries (for `x86_64`, `aarch64`, and + `armv8` architectures) rather than Docker images. This minimizes hassle and + total download size. Along the way, we switched libc to from `glibc` to + `musl` in the process. Please report any problems with the build or + instructions! -## 0.7.7 (2023-08-03) +## v0.7.7 (2023-08-03) * fix [#289](https://github.com/scottlamb/moonfire-nvr/issues/289): crash on pressing the `Add` button in the sample file directory dialog @@ -19,7 +24,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. * experimental (off by default) support for bundling UI files into the executable. -## 0.7.6 (2023-07-08) +## v0.7.6 (2023-07-08) * new log formats using `tracing`. This will allow richer context information. * bump minimum Rust version to 1.70. @@ -53,7 +58,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. * improvements to `moonfire-nvr config`, thanks to [@sky1e](https://github.com/sky1e). -## 0.7.5 (2022-05-09) +## v0.7.5 (2022-05-09) * [#219](https://github.com/scottlamb/moonfire-nvr/issues/219): fix live stream failing with `ws close: 1006` on URLs with port numbers. @@ -63,7 +68,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. Retina 0.3.10, improving compatibility with OMNY M5S2A 2812 cameras that send invalid `rtptime` values. -## 0.7.4 (2022-04-13) +## v0.7.4 (2022-04-13) * upgrade to Retina 0.3.9, improving camera interop and diagnostics. Fixes [#213](https://github.com/scottlamb/moonfire-nvr/issues/213), @@ -74,13 +79,13 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. * [#206](https://github.com/scottlamb/moonfire-nvr/issues/206#issuecomment-1086442543): fix `teardown Sender shouldn't be dropped: RecvError(())` errors on shutdown. -## 0.7.3 (2022-03-22) +## v0.7.3 (2022-03-22) * security fix: check the `Origin` header on live stream WebSocket requests to avoid cross-site WebSocket hijacking (CSWSH). * RTSP connections always use the Retina library rather than FFmpeg. -## 0.7.2 (2022-03-16) +## v0.7.2 (2022-03-16) * introduce a configuration file `/etc/moonfire-nvr.toml`; you will need to create one when upgrading. @@ -97,13 +102,13 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. * progress on [#70](https://github.com/scottlamb/moonfire-nvr/issues/184): shrink the binary from 154 MiB to 70 MiB by reducing debugging information. -## 0.7.1 (2021-10-27) +## v0.7.1 (2021-10-27) * bugfix: editing a camera from `nvr config` would erroneously clear the sample file directory associated with its streams. * RTSP transport (TCP or UDP) can be set per-stream from `nvr config`. -## 0.7.0 (2021-10-27) +## v0.7.0 (2021-10-27) * [schema version 7](guide/schema.md#version-7) * Changes to the [API](guide/api.md): @@ -130,7 +135,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. currently may be either absent or the string `record`. * Added `POST /api/users/` for altering a user's UI preferences. -## 0.6.7 (2021-10-20) +## v0.6.7 (2021-10-20) * trim whitespace when detecting time zone by reading `/etc/timezone`. * (Retina 0.3.2) better `TEARDOWN` handling with the default @@ -142,7 +147,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. `--rtsp-library=retina` (see [scottlamb/retina#25](https://github.com/scottlamb/retina/25)). -## 0.6.6 (2021-09-23) +## v0.6.6 (2021-09-23) * fix [#146](https://github.com/scottlamb/moonfire-nvr/issues/146): "init segment fetch error" when browsers have cached data from `v0.6.4` and @@ -167,7 +172,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. impatient to get fast results with ctrl-C when running interactively, rather than having to use `SIGKILL` from another terminal. -## 0.6.5 (2021-08-13) +## v0.6.5 (2021-08-13) * UI: improve video aspect ratio handling. Live streams formerly worked around a Firefox pixel aspect ratio bug by forcing all videos to 16:9, which @@ -181,7 +186,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. `GET_PARAMETERS` as a RTSP keepalive. GW Security cameras would ignored the latter, causing Moonfire NVR to drop the connection every minute. -## 0.6.4 (2021-06-28) +## v0.6.4 (2021-06-28) * Default to a new pure-Rust RTSP library, `retina`. If you hit problems, you can switch back via `--rtsp-library=ffmpeg`. Please report a bug if this @@ -189,7 +194,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. * Correct the pixel aspect ratio of 9:16 sub streams (eg a standard 16x9 camera rotated 90 degrees) in the same way as 16:9 sub streams. -## 0.6.3 (2021-03-31) +## v0.6.3 (2021-03-31) * New user interface! Besides a more modern appearance, it has better error handling and an experimental live view UI. @@ -199,7 +204,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. not calculated properly there might be unexpected gaps or overlaps in playback. -## 0.6.2 (2021-03-12) +## v0.6.2 (2021-03-12) * Fix panics when a stream's PTS has extreme jumps ([#113](https://github.com/scottlamb/moonfire-nvr/issues/113)) @@ -209,7 +214,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. `moonfire-nvr check --delete-orphan-rows` command from actually deleting rows. -## 0.6.1 (2021-02-16) +## v0.6.1 (2021-02-16) * Improve the server's error messages on the console and in logs. * Switch the UI build from the `yarn` package manager to `npm`. @@ -221,7 +226,7 @@ even on minor releases, e.g. `0.7.5` -> `0.7.6`. * Fix mangled favicons ([#105](https://github.com/scottlamb/moonfire-nvr/issues/105)) -## 0.6.0 (2021-01-22) +## v0.6.0 (2021-01-22) This is the first tagged version and first Docker image release. I chose the version number 0.6.0 to match the current schema version 6. diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index dc1aacdb..00000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,75 +0,0 @@ -# syntax=docker/dockerfile:1.2.1 -# This file is part of Moonfire NVR, a security camera network video recorder. -# Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. -# SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception. - -# See documentation here: -# https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md - -# "dev-common" is the portion of "dev" (see below) which isn't specific to the -# target arch. It's sufficient for building the non-arch-specific webpack. -FROM --platform=$BUILDPLATFORM ubuntu:20.04 AS dev-common -LABEL maintainer="slamb@slamb.org" -ARG BUILD_UID=1000 -ARG BUILD_GID=1000 -ARG INVALIDATE_CACHE_DEV_COMMON= -ENV LC_ALL=C.UTF-8 -COPY docker/dev-common.bash / -RUN --mount=type=cache,id=var-cache-apt,target=/var/cache/apt,sharing=locked \ - /dev-common.bash -CMD [ "/bin/bash", "--login" ] - -# "dev" is a full development environment, suitable for shelling into or -# using with the VS Code container plugin. -FROM --platform=$BUILDPLATFORM dev-common AS dev -LABEL maintainer="slamb@slamb.org" -ARG BUILDARCH -ARG TARGETARCH -ARG INVALIDATE_CACHE_DEV= -COPY docker/dev.bash / -RUN --mount=type=cache,id=var-cache-apt,target=/var/cache/apt,sharing=locked \ - /dev.bash -USER moonfire-nvr:moonfire-nvr -WORKDIR /var/lib/moonfire-nvr - -# Build the UI with node_modules and ui-dist outside the src dir. -FROM --platform=$BUILDPLATFORM dev-common AS build-ui -ARG INVALIDATE_CACHE_BUILD_UI= -LABEL maintainer="slamb@slamb.org" -WORKDIR /var/lib/moonfire-nvr/src/ui -COPY docker/build-ui.bash / -COPY ui /var/lib/moonfire-nvr/src/ui -RUN --mount=type=tmpfs,target=/var/lib/moonfire-nvr/src/ui/node_modules \ - /build-ui.bash - -# Build the Rust components. Note that dev.sh set up an environment variable -# in .buildrc that similarly changes the target dir path. -FROM --platform=$BUILDPLATFORM dev AS build-server -LABEL maintainer="slamb@slamb.org" -ARG INVALIDATE_CACHE_BUILD_SERVER= -COPY docker/build-server.bash / -COPY --from=build-ui /var/lib/moonfire-nvr/src/ui/build /var/lib/moonfire-nvr/src/ui/build -RUN --mount=type=cache,id=target,target=/var/lib/moonfire-nvr/target,sharing=locked,mode=777 \ - --mount=type=cache,id=cargo,target=/cargo-cache,sharing=locked,mode=777 \ - --mount=type=bind,source=server,target=/var/lib/moonfire-nvr/src/server,readonly \ - /build-server.bash - -# Deployment environment, now in the target platform. -FROM --platform=$TARGETPLATFORM ubuntu:20.04 AS deploy -LABEL maintainer="slamb@slamb.org" -ARG INVALIDATE_CACHE_BUILD_DEPLOY= -ENV LC_ALL=C.UTF-8 -COPY docker/deploy.bash / -RUN --mount=type=cache,id=var-cache-apt,target=/var/cache/apt,sharing=locked \ - /deploy.bash -COPY --from=dev-common /docker-build-debug/dev-common/ /docker-build-debug/dev-common/ -COPY --from=dev /docker-build-debug/dev/ /docker-build-debug/dev/ -COPY --from=build-server /docker-build-debug/build-server/ /docker-build-debug/build-server/ -COPY --from=build-server /usr/local/bin/moonfire-nvr /usr/local/bin/moonfire-nvr -COPY --from=build-ui /docker-build-debug/build-ui /docker-build-debug/build-ui - -# The install instructions say to use --user in the docker run commandline. -# Specify a non-root user just in case someone forgets. -USER 10000:10000 -WORKDIR /var/lib/moonfire-nvr -ENTRYPOINT [ "/usr/local/bin/moonfire-nvr" ] diff --git a/docker/build-server.bash b/docker/build-server.bash deleted file mode 100755 index 0081c158..00000000 --- a/docker/build-server.bash +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# This file is part of Moonfire NVR, a security camera network video recorder. -# Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. -# SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception. - -# Build the "build-server" target. See Dockerfile. - -set -o errexit -set -o pipefail -set -o xtrace - -mkdir /docker-build-debug/build-server -exec > >(tee -i /docker-build-debug/build-server/output) 2>&1 -date -uname -a -find /cargo-cache -ls > /docker-build-debug/build-server/cargo-cache-before -find ~/target -ls > /docker-build-debug/build-server/target-before - -source ~/.buildrc - -# The "mode" argument to cache mounts doesn't seem to work reliably -# (as of Docker version 20.10.5, build 55c4c88, using a docker-container -# builder), thus the chmod command. -sudo chmod 777 /cargo-cache /var/lib/moonfire-nvr/target -mkdir -p /cargo-cache/{git,registry} -ln -s /cargo-cache/{git,registry} ~/.cargo - -build_profile=release-lto -cd src/server -time cargo test --features=bundled-ui -time cargo build --features=bundled-ui --profile=$build_profile -find /cargo-cache -ls > /docker-build-debug/build-server/cargo-cache-after -find ~/target -ls > /docker-build-debug/build-server/target-after -sudo install -m 755 \ - ~/platform-target/$build_profile/moonfire-nvr \ - /usr/local/bin/moonfire-nvr -date diff --git a/docker/build-ui.bash b/docker/build-ui.bash deleted file mode 100755 index a9fe107c..00000000 --- a/docker/build-ui.bash +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# This file is part of Moonfire NVR, a security camera network video recorder. -# Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. -# SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception. - -# Build the "build-ui" target. See Dockerfile. - -set -o errexit -set -o pipefail -set -o xtrace - -mkdir /docker-build-debug/build-ui -exec > >(tee -i /docker-build-debug/build-ui/output) 2>&1 - -date -uname -a -node --version -npm --version -time npm ci -time npm run build - -find /var/lib/moonfire-nvr/src/ui/node_modules -ls \ - > /docker-build-debug/build-ui/node_modules-after -date diff --git a/docker/deploy.bash b/docker/deploy.bash deleted file mode 100755 index d8973686..00000000 --- a/docker/deploy.bash +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# This file is part of Moonfire NVR, a security camera network video recorder. -# Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. -# SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception. - -# Build the "deploy" target. See Dockerfile. - -set -o errexit -set -o pipefail -set -o xtrace - -mkdir -p /docker-build-debug/deploy -exec > >(tee -i /docker-build-debug/deploy/output) 2>&1 -find /var/cache/apt -ls > /docker-build-debug/deploy/var-cache-apt-before - -date -uname -a -export DEBIAN_FRONTEND=noninteractive -time apt-get update -time apt-get install --assume-yes --no-install-recommends \ - libncurses6 \ - libncursesw6 \ - locales \ - sudo \ - sqlite3 \ - tzdata \ - vim-nox -rm -rf /var/lib/apt/lists/* -ln -s moonfire-nvr /usr/local/bin/nvr - -find /var/cache/apt -ls > /docker-build-debug/deploy/var-cache-apt-after -date diff --git a/docker/dev-common.bash b/docker/dev-common.bash deleted file mode 100755 index fb265491..00000000 --- a/docker/dev-common.bash +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/bash -# This file is part of Moonfire NVR, a security camera network video recorder. -# Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. -# SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception. - -# Build the "dev" target. See Dockerfile. - -set -o errexit -set -o pipefail -set -o xtrace - -mkdir --mode=1777 /docker-build-debug -mkdir /docker-build-debug/dev-common -exec > >(tee -i /docker-build-debug/dev-common/output) 2>&1 - -date -uname -a -find /var/cache/apt -ls > /docker-build-debug/dev-common/var-cache-apt-before - -export DEBIAN_FRONTEND=noninteractive - -# This file cleans apt caches after every invocation. Instead, we use a -# buildkit cachemount to avoid putting them in the image while still allowing -# some reuse. -rm /etc/apt/apt.conf.d/docker-clean - -packages=() - -# Install all packages necessary for building (and some for testing/debugging). -packages+=( - build-essential - curl - pkgconf - locales - sudo - sqlite3 - tzdata - vim-nox -) -time apt-get update -time apt-get install --assume-yes --no-install-recommends "${packages[@]}" - -# Install a more recent nodejs/npm than in the universe repository. -time curl -sL https://deb.nodesource.com/setup_14.x | bash - -time apt-get install nodejs - -# Create the user. On the dev environment, allow sudo. -groupadd \ - --gid="${BUILD_GID}" \ - moonfire-nvr -useradd \ - --no-log-init \ - --home-dir=/var/lib/moonfire-nvr \ - --uid="${BUILD_UID}" \ - --gid=moonfire-nvr \ - --shell=/bin/bash \ - --create-home \ - moonfire-nvr -echo 'moonfire-nvr ALL=(ALL) NOPASSWD: ALL' >>/etc/sudoers - -# Install Rust. Note curl was already installed for yarn above. -time su moonfire-nvr -lc " - curl --proto =https --tlsv1.2 -sSf https://sh.rustup.rs | - sh -s - -y" - -# Put configuration for the Rust build into a new ".buildrc" which is used -# both (1) interactively from ~/.bashrc when logging into the dev container -# and (2) from a build-server RUN command. In particular, the latter can't -# use ~/.bashrc because that script immediately exits when run from a -# non-interactive shell. -echo 'source $HOME/.buildrc' >> /var/lib/moonfire-nvr/.bashrc -cat >> /var/lib/moonfire-nvr/.buildrc < /docker-build-debug/dev-common/var-cache-apt-after -date diff --git a/docker/dev.bash b/docker/dev.bash deleted file mode 100755 index 4f7bc687..00000000 --- a/docker/dev.bash +++ /dev/null @@ -1,122 +0,0 @@ -#!/bin/bash -# This file is part of Moonfire NVR, a security camera network video recorder. -# Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. -# SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception. - -# Build the "dev" target. See Dockerfile. - -set -o errexit -set -o pipefail -set -o xtrace - -mkdir /docker-build-debug/dev -exec > >(tee -i /docker-build-debug/dev/output) 2>&1 - -date -uname -a -find /var/cache/apt -ls > /docker-build-debug/dev/var-cache-apt-before - -export DEBIAN_FRONTEND=noninteractive - -packages=() - -if [[ "${BUILDARCH}" != "${TARGETARCH}" ]]; then - # Set up cross compilation. - case "${TARGETARCH}" in - arm64) - dpkg_arch=arm64 - gcc_target=aarch64-linux-gnu - rust_target=aarch64-unknown-linux-gnu - target_is_port=1 - ;; - arm) - dpkg_arch=armhf - gcc_target=arm-linux-gnueabihf - rust_target=arm-unknown-linux-gnueabihf - target_is_port=1 - ;; - amd64) - dpkg_arch=amd64 - gcc_target=x86_64-linux-gnu - rust_target=x86_64-unknown-linux-gnu - target_is_port=0 - ;; - *) - echo "Unsupported cross-compile target ${TARGETARCH}." >&2 - exit 1 - esac - apt_target_suffix=":${dpkg_arch}" - case "${BUILDARCH}" in - amd64|386) host_is_port=0 ;; - *) host_is_port=1 ;; - esac - - time dpkg --add-architecture "${dpkg_arch}" - - if [[ $target_is_port -ne $host_is_port ]]; then - # Ubuntu stores non-x86 architectures at a different URL, so futz the - # sources file to allow installing both host and target. - # See https://github.com/rust-embedded/cross/blob/master/docker/common.sh - perl -pi.bak -e ' - s{^deb (http://.*.ubuntu.com/ubuntu/) (.*)} - {deb [arch=amd64,i386] \1 \2\ndeb [arch-=amd64,i386] http://ports.ubuntu.com/ubuntu-ports \2}; - s{^deb (http://ports.ubuntu.com/ubuntu-ports/) (.*)} - {deb [arch=amd64,i386] http://archive.ubuntu.com/ubuntu \2\ndeb [arch-=amd64,i386] \1 \2}' \ - /etc/apt/sources.list - cat /etc/apt/sources.list - fi - - packages+=( - g++-${gcc_target/_/-} - libc6-dev-${dpkg_arch}-cross - qemu-user - ) -fi - -time apt-get update - -# Install the packages for the target architecture. -packages+=( - libncurses-dev"${apt_target_suffix}" - libsqlite3-dev"${apt_target_suffix}" -) -time apt-get update -time apt-get install --assume-yes --no-install-recommends "${packages[@]}" - -# Set environment variables for cross-compiling. -# Also set up a symlink that points to the output platform's target dir, because -# the target directory layout varies when cross-compiling, as described here: -# https://doc.rust-lang.org/cargo/guide/build-cache.html -if [[ -n "${rust_target}" ]]; then - su moonfire-nvr -lc "rustup target install ${rust_target} && - ln -s target/${rust_target} platform-target" - underscore_rust_target="${rust_target//-/_}" - uppercase_underscore_rust_target="${underscore_rust_target^^}" - cat >> /var/lib/moonfire-nvr/.buildrc <_ prefix that the README.md -# describes for other vars. Fortunately Moonfire NVR doesn't have any host tools -# that need pkg-config. -export PKG_CONFIG=${gcc_target}-pkg-config - -# https://github.com/alexcrichton/cc-rs uses these variables to decide what -# compiler to invoke. -export CC_${underscore_rust_target}=${gcc_target}-gcc -export CXX_${underscore_rust_target}=${gcc_target}-g++ -EOF -else - su moonfire-nvr -lc "ln -s target platform-target" -fi - -find /var/cache/apt -ls > /docker-build-debug/dev/var-cache-apt-after -date diff --git a/guide/build.md b/guide/build.md index d1c016b7..d184f82f 100644 --- a/guide/build.md +++ b/guide/build.md @@ -2,7 +2,7 @@ This document has notes for software developers on building Moonfire NVR from source code for development. If you just want to install precompiled -binaries, see the [Docker installation instructions](install.md) instead. +binaries, see the [installation instructions](install.md) instead. This document doesn't spell out as many details as the installation instructions. Please ask on Moonfire NVR's [issue @@ -11,11 +11,9 @@ tracker](https://github.com/scottlamb/moonfire-nvr/issues) or stuck. Please also send pull requests to improve this doc. * [Downloading](#downloading) -* [Docker builds](#docker-builds) - * [Release procedure](#release-procedure) -* [Non-Docker setup](#non-docker-setup) +* [Building](#building) * [Running interactively straight from the working copy](#running-interactively-straight-from-the-working-copy) - * [Running as a `systemd` service](#running-as-a-systemd-service) +* [Release procedure](#release-procedure) ## Downloading @@ -28,152 +26,16 @@ $ git clone https://github.com/scottlamb/moonfire-nvr.git $ cd moonfire-nvr ``` -## Docker builds +## Building -This command should prepare a deployment image for your local machine: - -```console -$ sudo docker buildx build --load --tag=moonfire-nvr -f docker/Dockerfile . -``` - -
- Common errors - -* `docker: 'buildx' is not a docker command.`: You shouldn't see this with - Docker 20.10. With Docker version 19.03 you'll need to prepend - `DOCKER_CLI_EXPERIMENTAL=enabled` to `docker buildx build` commands. If - your Docker version is older than 19.03, you'll need to upgrade. -* `At least one invalid signature was encountered.`: this is likely - due to an error in `libseccomp`, as described [in this askubuntu.com answer](https://askubuntu.com/a/1264921/1365248). - Try running in a privileged builder. As described in [`docker buildx build` documentation](https://docs.docker.com/engine/reference/commandline/buildx_build/#allow), - run this command once: - ```console - $ sudo docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure' - ``` - then add `--allow security.insecure` to your `docker buildx build` commandlines. -
- -If you want to iterate on code changes, doing a full Docker build from -scratch every time will be painfully slow. You will likely find it more -helpful to use the `dev` target. This is a self-contained developer environment -which you can use from its shell via `docker run` or via something like -Visual Studio Code's Docker plugin. - -```console -$ sudo docker buildx build \ - --load --tag=moonfire-dev --target=dev -f docker/Dockerfile . -... -$ sudo docker run \ - --rm --interactive=true --tty \ - --mount=type=bind,source=$(pwd),destination=/var/lib/moonfire-nvr/src \ - moonfire-dev -``` - -The development image overrides cargo's output directory to -`/var/lib/moonfire-nvr/target`. (See `~moonfire-nvr/.buildrc`.) This avoids -using a bind filesystem for build products, which can be slow on macOS. It -also means that if you sometimes compile directly on the host and sometimes -within Docker, they don't trip over each other's target directories. - -You can also cross-compile to a different architecture. Adding a -`--platform=linux/arm64/v8,linux/arm/v7,linux/amd64` argument will compile -Moonfire NVR for all supported platforms. (Note: this has been used -successfully for building on x86-64 and compiling to arm but not the -reverse.) For the `dev` target, this prepares a build which executes on your -local architecture and is capable of building a binary for your desired target -architecture. - -On the author's macOS machine with Docker desktop 3.0.4, building for -multiple platforms at once will initially fail with the following error: - -```console -$ sudo docker buildx build ... --platform=linux/arm64/v8,linux/arm/v7,linux/amd64 -[+] Building 0.0s (0/0) -error: multiple platforms feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use") -``` - -Running `docker buildx create --use` once solves this problem, with a couple -caveats: - -* you'll need to specify an additional `--load` argument to make builds - available to run locally. -* the `--load` argument only works for one platform at a time. With multiple - platforms, it gives an error like the following: - ``` - error: failed to solve: rpc error: code = Unknown desc = docker exporter does not currently support exporting manifest lists - ``` - [A comment on docker/buildx issue - #59](https://github.com/docker/buildx/issues/59#issuecomment-667548900) - suggests a workaround of building all three then using caching to quickly - load the one of immediate interest: - ``` - $ sudo docker buildx build --platform=linux/arm64/v8,linux/arm/v7,linux/amd64 ... - $ sudo docker buildx build --load --platform=arm64/v8 ... - ``` - -On Linux hosts (as opposed to when using Docker Desktop on macOS/Windows), -you'll likely see errors like the ones below. The solution is to [install -emulators](https://github.com/tonistiigi/binfmt#installing-emulators). -You may need to reinstall emulators on each boot of the host. - -``` -Exec format error - -Error while loading /usr/sbin/dpkg-split: No such file or directory -Error while loading /usr/sbin/dpkg-deb: No such file or directory -``` - -Moonfire NVR's `Dockerfile` has some built-in debugging tools: - -* Each stage saves some debug info to `/docker-build-debug/`, and - the `deploy` stage preserves the output from previous stages. The debug - info includes: - * output (stdout + stderr) from the build script, running long operations - through the `time` command. - * `find -ls` output on cache mounts before and after. -* Each stage accepts a `INVALIDATE_CACHE_` argument. You can use eg - `--build-arg=INVALIDATE_CACHE_BUILD_SERVER=$(date +%s)` to force the - `build-server` stage to be rebuilt rather than use cached Docker layers. - -### Release procedure - -Releases are currently a bit manual. From a completely clean git work tree, - -1. manually verify the current commit is pushed to github's master branch and - has a green checkmark indicating CI passed. -2. update version in `CHANGELOG.md`. -3. run commands: - ```bash - VERSION=x.y.z - git commit -am "prepare version ${VERSION}" - git tag -a "v${VERSION}" -m "version ${VERSION}" - ./release.bash - git push - git push origin "v${VERSION}" - ``` - -The `release.bash` script needs [`jq`](https://stedolan.github.io/jq/) -installed to work. - -## Non-Docker setup - -You may prefer building without Docker on the host. Moonfire NVR should run -natively on any Unix-like system. It's been tested on Linux and macOS. -(In theory [Windows Subsystem for +Moonfire NVR should run natively on any Unix-like system. It's been tested on +Linux, macOS, and FreeBSD. (In theory [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/about) should also work. Please speak up if you try it.) -On macOS systems native builds may be noticeably faster than using Docker's -Linux VM and filesystem overlay. - -To build the server, you will need the following C libraries installed: - -* [SQLite3](https://www.sqlite.org/), at least version 3.8.2. - (You can skip this if you compile with `--features=bundled` and - don't mind the `moonfire-nvr sql` command not working.) - -* [`ncursesw`](https://www.gnu.org/software/ncurses/), the UTF-8 version of - the `ncurses` library. +To build the server, you will need [SQLite3](https://www.sqlite.org/). You +can skip this if compiling with `--features=rusqlite/bundled` and don't +mind the `moonfire-nvr sql` command not working. To build the UI, you'll need a [nodejs](https://nodejs.org/en/download/) release in "Maintenance" or "LTS" status: currently v14, v16, or v18. @@ -184,7 +46,6 @@ most non-Rust dependencies: ```console $ sudo apt-get install \ build-essential \ - libncurses-dev \ libsqlite3-dev \ pkgconf \ sqlite3 \ @@ -253,72 +114,24 @@ $ nvr run with `cargo build` rather than `cargo build --release`, for a faster build cycle and slower performance.) -Note this `nvr` is a little different than the `nvr` shell script you create -when following the [install instructions](install.md). With that shell wrapper, -`nvr run` will create and run a detached Docker container with some extra -arguments specified in the script. This `nvr run` will directly run from the -terminal, with no extra arguments, until you abort with Ctrl-C. Likewise, -some of the shell script's subcommands that wrap Docker (`start`, `stop`, and -`logs`) have no parallel with this `nvr`. - -### Running as a `systemd` service - -If you want to deploy a non-Docker build on Linux, you may want to use -`systemd`. Create `/etc/systemd/system/moonfire-nvr.service`: - -```ini -[Unit] -Description=Moonfire NVR -After=network-online.target - -[Service] -ExecStart=/usr/local/bin/moonfire-nvr run -Environment=TZ=:/etc/localtime -Environment=MOONFIRE_FORMAT=google-systemd -Environment=MOONFIRE_LOG=info -Environment=RUST_BACKTRACE=1 -Type=simple -User=moonfire-nvr -Restart=on-failure -CPUAccounting=true -MemoryAccounting=true -BlockIOAccounting=true - -[Install] -WantedBy=multi-user.target -``` - -You'll also need a `/etc/moonfire-nvr.toml`: - -```toml -[[binds]] -ipv4 = "0.0.0.0:8080" -allowUnauthenticatedPermissions = { viewVideo = true } - -[[binds]] -unix = "/var/lib/moonfire-nvr/sock" -ownUidIsPrivileged = true -``` - -Note this configuration is insecure. You can change that via replacing the -`allowUnauthenticatedPermissions` here as described in [Securing Moonfire NVR -and exposing it to the Internet](secure.md). +## Release procedure -See [ref/config.md](../ref/config.md) for more about `/etc/moonfire-nvr.toml`. - -Some handy commands: +Releases are currently a bit manual. From a completely clean git work tree, -```console -$ sudo systemctl daemon-reload # reload configuration files -$ sudo systemctl start moonfire-nvr # start the service now -$ sudo systemctl stop moonfire-nvr # stop the service now (but don't wait for it finish stopping) -$ sudo systemctl status moonfire-nvr # show if the service is running and the last few log lines -$ sudo systemctl enable moonfire-nvr # start the service on boot -$ sudo systemctl disable moonfire-nvr # don't start the service on boot -$ sudo journalctl --unit=moonfire-nvr --since='-5 min' --follow # look at recent logs and await more -``` +1. manually verify the current commit is pushed to github's master branch and + has a green checkmark indicating CI passed. +2. update versions: + * update `server/Cargo.toml` version by hand; run `cargo test --workspace` + to update `Cargo.lock`. + * ensure `README.md` and `CHANGELOG.md` refer to the new version. +3. run commands: + ```bash + VERSION=x.y.z + git commit -am "prepare version ${VERSION}" + git tag -a "v${VERSION}" -m "version ${VERSION}" + git push origin "v${VERSION}" + ``` -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. +The rest should happen automatically—the tag push will fire off a GitHub +Actions workflow which creates a release, cross-compiles statically compiled +binaries for three different platforms, and uploads them to the release. diff --git a/guide/install.md b/guide/install.md index 1500cdd6..1651b52a 100644 --- a/guide/install.md +++ b/guide/install.md @@ -1,14 +1,14 @@ # Installing Moonfire NVR -* [Downloading, installing, and configuring Moonfire NVR with Docker](#downloading-installing-and-configuring-moonfire-nvr-with-docker) +* [Downloading, installing, and configuring Moonfire NVR](#downloading-installing-and-configuring-moonfire-nvr) * [Dedicated hard drive setup](#dedicated-hard-drive-setup) * [Completing configuration through the UI](#completing-configuration-through-the-ui) * [Starting it up](#starting-it-up) -## Downloading, installing, and configuring Moonfire NVR with Docker +## Downloading, installing, and configuring Moonfire NVR This document describes how to download, install, and configure Moonfire NVR -via the prebuilt Docker images available for x86-64, arm64, and arm. If you +via the prebuilt Linux binaries available for x86-64, arm64, and arm. If you instead want to build Moonfire NVR yourself, see the [Build instructions](build.md). @@ -18,17 +18,16 @@ left, and pick the [latest tagged version](https://github.com/scottlamb/moonfire ![Selecting a version of install instructions](install-version.png) -Next, install [Docker](https://www.docker.com/) if you haven't already, -and verify `sudo docker run --rm hello-world` works. +Download the binary for your platform from the matching GitHub release. +Install it as `/usr/local/bin/moonfire-nvr` and ensure it is executable, e.g. +for version `v0.7.8` on Intel machines: -
- sudo or not? - -If you prefer to save typing by not prefixing all `docker` and `nvr` commands -with `sudo`, see [Docker docs: Manage Docker as a non-root -user](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user). -Note `docker` access is equivalent to root access security-wise. -
+```console +$ VERSION=v0.7.8 +$ ARCH=x86_64-unknown-linux-musl +$ curl -OL "https://github.com/scottlamb/moonfire-nvr/releases/download/$VERSION/moonfire-nvr-$VERSION-$ARCH" +$ sudo install -m 755 "moonfire-nvr-$VERSION-$ARCH" /usr/local/bin/moonfire-nvr +``` Next, you'll need to set up your filesystem and the Moonfire NVR user. @@ -49,22 +48,12 @@ On most Linux systems, you can create the user as follows: $ sudo useradd --user-group --create-home --home /var/lib/moonfire-nvr moonfire-nvr ``` -and create a script called `nvr` to run Moonfire NVR as the intended host user. -This script supports running Moonfire NVR's various administrative commands interactively -and managing a long-lived Docker container for its web interface. - -As you set up this script, adjust the `tz` variable as appropriate for your -time zone. - -Use your favorite editor to create `/etc/moonfire-nvr.toml` and -`/usr/local/bin/nvr`, starting from the configurations below: +Use your favorite editor to create `/etc/moonfire-nvr.toml`, +starting from the configurations below: ```console $ sudo nano /etc/moonfire-nvr.toml (see below for contents) -$ sudo nano /usr/local/bin/nvr -(see below for contents) -$ sudo chmod a+rx /usr/local/bin/nvr ``` `/etc/moonfire-nvr.toml` (see [ref/config.md](../ref/config.md) for more explanation): @@ -78,79 +67,10 @@ unix = "/var/lib/moonfire-nvr/sock" ownUidIsPrivileged = true ``` -`/usr/local/bin/nvr`: -```bash -#!/bin/bash -e - -# Set your timezone here. -tz="America/Los_Angeles" - -image_name="scottlamb/moonfire-nvr:v0.7.7" -container_name="moonfire-nvr" -common_docker_run_args=( - --mount=type=bind,source=/var/lib/moonfire-nvr,destination=/var/lib/moonfire-nvr - --mount=type=bind,source=/etc/moonfire-nvr.toml,destination=/etc/moonfire-nvr.toml - - # Add additional mount lines here for each sample file directory - # outside of /var/lib/moonfire-nvr, e.g.: - # --mount=type=bind,source=/media/nvr/sample,destination=/media/nvr/sample - - --user="$(id -u moonfire-nvr):$(id -g moonfire-nvr)" - - # This avoids errors with broken seccomp on older 32-bit hosts. - # https://github.com/moby/moby/issues/40734 - --security-opt=seccomp:unconfined - - # This is the simplest way of configuring networking, although - # you can use e.g. --publish=8080:8080 in the run) case below if you - # prefer. - --network=host - - # docker's default log driver won't rotate logs properly, and will throw - # away logs when you destroy and recreate the container. Using journald - # solves these problems. - # https://docs.docker.com/config/containers/logging/configure/ - --log-driver=journald - --log-opt="tag=moonfire-nvr" - - --env=RUST_BACKTRACE=1 - --env=TZ=":${tz}" -) - -case "$1" in -run) - shift - exec docker run \ - --detach=true \ - --restart=unless-stopped \ - "${common_docker_run_args[@]}" \ - --name="${container_name}" \ - "${image_name}" \ - run \ - "$@" - ;; -start|stop|logs|rm) - exec docker "$@" "${container_name}" - ;; -pull) - exec docker pull "${image_name}" - ;; -*) - exec docker run \ - --interactive=true \ - --tty \ - --rm \ - "${common_docker_run_args[@]}" \ - "${image_name}" \ - "$@" - ;; -esac -``` - -then try it out by initializing the database: +Then initialize the database: ```console -$ sudo nvr init +$ sudo -u moonfire-nvr moonfire-nvr init ``` This will create a directory `/var/lib/moonfire-nvr/db` with a SQLite3 database @@ -189,10 +109,6 @@ system will boot successfully even when the hard drive is unavailable (such as when your external USB storage is unmounted). This can be helpful when recovering from problems. -Add a new `--mount` line to your Docker wrapper script `/usr/local/bin/nvr` -to expose the new sample directory `/media/nvr/sample` to the Docker container, -right where a comment mentions "Additional mount lines". - ### Completing configuration through the UI Once your system is set up, it's time to initialize an empty database @@ -200,16 +116,17 @@ and add the cameras and sample directories. You can do this by using the `moonfire-nvr` binary's text-based configuration tool. ```console -$ sudo nvr config 2>debug-log +$ sudo -u moonfire-nvr moonfire-nvr config 2>debug-log ```
Did it return without doing anything? -If `nvr config` returns you to the console prompt right away, look in the -`debug-log` file for why. One common reason is that you have Moonfire NVR -running; you'll need to shut it down first. Try `nvr stop` before `nvr config` -and `nvr start` afterward. +If `moonfire-nvr config` returns you to the console prompt right away, look in +the `debug-log` file for why. One common reason is that you have Moonfire NVR +running; you'll need to shut it down first. If you are running a systemd +service as described below, try `sudo systemctl stop moonfire-nvr` before +editing the config and `sudo systemctl start moonfire-nvr` after.
In the user interface, @@ -277,15 +194,58 @@ starting it in this configuration to try it out, particularly if the machine it's running on is behind a home router's firewall. You might not; in that case read through [secure the system](secure.md) first. -This command will start a detached Docker container for the web interface. -It will automatically restart when your system does. +Assuming you want to proceed, you can launch Moonfire NVR through `systemd`. +Create `/etc/systemd/system/moonfire-nvr.service`: + +```ini +[Unit] +Description=Moonfire NVR +After=network-online.target + +# If you use an external hard drive, uncomment this with a reference to the +# mount point as written in `/etc/fstab`. +# RequiresMountsFor=/media/nvr + +[Service] +ExecStart=/usr/local/bin/moonfire-nvr run +Environment=TZ=:/etc/localtime +Environment=MOONFIRE_FORMAT=systemd +Environment=MOONFIRE_LOG=info +Environment=RUST_BACKTRACE=1 +Type=simple +User=moonfire-nvr +Restart=on-failure +CPUAccounting=true +MemoryAccounting=true +BlockIOAccounting=true + +[Install] +WantedBy=multi-user.target +``` + +Then start it up as follows: + +```console +$ sudo systemctl daemon-reload # read in the new config file +$ sudo systemctl enable --now moonfire-nvr # start the service now and on boot +``` + +Some handy commands: ```console -$ sudo nvr run +$ sudo systemctl daemon-reload # reload configuration files +$ sudo systemctl start moonfire-nvr # start the service now without enabling on boot +$ sudo systemctl stop moonfire-nvr # stop the service now (but don't wait for it finish stopping) +$ sudo systemctl status moonfire-nvr # show if the service is running and the last few log lines +$ sudo systemctl enable moonfire-nvr # start the service on boot +$ sudo systemctl disable moonfire-nvr # don't start the service on boot +$ sudo journalctl --unit=moonfire-nvr --since='-5 min' --follow # look at recent logs and await more ``` -You can temporarily disable the service via `nvr stop` and restart it later via -`nvr start`. You'll need to do this before and after using `nvr config`. +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. The HTTP interface is accessible on port 8080; if your web browser is running on the same machine, you can access it at diff --git a/guide/schema.md b/guide/schema.md index 9b87a648..def35eb5 100644 --- a/guide/schema.md +++ b/guide/schema.md @@ -62,11 +62,11 @@ SQLite database: no longer in the dangerous mode. Next ensure Moonfire NVR is not running and does not automatically restart if -the system is rebooted during the upgrade. If you followed the Docker +the system is rebooted during the upgrade. If you followed the standard instructions, you can do this as follows: ```console -$ sudo nvr stop +$ sudo systemctl disable --now moonfire-nvr ``` Then back up your SQLite database. If you are using the default path, you can @@ -92,34 +92,27 @@ manual for write-ahead logging](https://www.sqlite.org/wal.html): Run the upgrade procedure using the new software binary. -```console -$ sudo nvr pull # updates the docker image to the latest binary -$ sudo nvr upgrade # runs the upgrade -``` - As a rule of thumb, on a Raspberry Pi 4 with a 1 GiB database, an upgrade might take about four minutes for each schema version and for the final vacuum. Next, you can run the system in read-only mode, although you'll find this only works in the "insecure" setup. (Authorization requires writing the database.) +To just run directly within the console until you hit ctrl-C, use the following +command: ```console -$ sudo nvr rm -$ sudo nvr run --read-only +$ sudo -u moonfire-nvr moonfire-nvr run --read-only ``` Go to the web interface and ensure the system is operating correctly. If you detect a problem now, you can copy the old database back over the new one -and edit your `nvr` script to use the corresponding older Docker image. If -you detect a problem after enabling read-write operation, a restore will be -more complicated. +and go back to the prior release. If you detect a problem after enabling +read-write operation, a restore will be more complicated. -Once you're satisfied, restart the system in read-write mode: +Once you're satisfied, ctrl-C and start the system in read-write mode: ```console -$ sudo nvr stop -$ sudo nvr rm -$ sudo nvr run +$ sudo systemctl enable --now moonfire-nvr ``` Hopefully your system is functioning correctly. If not, there are two options @@ -137,7 +130,8 @@ for restore; neither are easy: * undo the changes by hand. There's no documentation on this; you'll need to read the code and come up with a reverse transformation. -The `nvr check` command will show you what problems exist on your system. +The `sudo -u moonfire-nvr moonfire-nvr check` command will show you what +problems exist on your system. ### Unversioned to version 0 diff --git a/guide/secure.md b/guide/secure.md index 67ad4be8..8b417d5d 100644 --- a/guide/secure.md +++ b/guide/secure.md @@ -161,8 +161,8 @@ your browser. See [How to secure Nginx with Let's Encrypt on Ubuntu ## 6. Reconfigure Moonfire NVR -If you follow the recommended Docker setup, your `/etc/moonfire-nvr.toml` -will contain this line: +If you follow the recommended setup, your `/etc/moonfire-nvr.toml` will contain +this line: ```toml allowUnauthenticatedPermissions = { viewVideo = true } @@ -185,18 +185,16 @@ This change has two effects: See also [ref/config.md](../ref/config.md) for more about the configuration file. If the webserver is running on the same machine as Moonfire NVR, you might -also change `--publish=8080:8080` to `--publish=127.0.0.1:8080:8080` in your -`/usr/local/bin/nvr` script, preventing other machines on the network from -impersonating the proxy, effectively allowing them to lie about the client's IP -and protocol. +also change the `ipv4 = "0.0.0.0:8080"` line in `/etc/moonfire-nvr/toml` to +`ipv4 = "127.0.0.1:8080"`, so that only the local host can directly connect to +Moonfire NVR. If other machines can connect directly, they can impersonate +the proxy, which would effectively allow them to lie about the client's IP and +protocol. -To make this take effect, you'll need to stop the running Docker container, -delete it, and create/run a new one: +To make this take effect, you'll need to restart Moonfire NVR: ```console -$ sudo nvr stop -$ sudo nvr rm -$ sudo nvr run +$ sudo systemctl restart moonfire-nvr ``` ## 7. Configure the webserver diff --git a/guide/troubleshooting.md b/guide/troubleshooting.md index d3224e69..cd35f6d4 100644 --- a/guide/troubleshooting.md +++ b/guide/troubleshooting.md @@ -11,7 +11,6 @@ need more help. * [Camera stream errors](#camera-stream-errors) * [Problems](#problems) * [Server errors](#server-errors) - * [`clock_gettime failed: EPERM: Operation not permitted`](#clock_gettime-failed-eperm-operation-not-permitted) * [`Error: pts not monotonically increasing; got 26615520 then 26539470`](#error-pts-not-monotonically-increasing-got-26615520-then-26539470) * [Out of disk space](#out-of-disk-space) * [Database or filesystem corruption errors](#database-or-filesystem-corruption-errors) @@ -29,9 +28,8 @@ While Moonfire NVR is running, logs will be written to stderr. * When running the configuration UI, you typically should redirect stderr to a text file to avoid poor interaction between the interactive stdout output and the logging. If you use the recommended - `nvr config 2>debug-log` command, output will be in the `debug-log` file. -* When running detached through Docker, Docker saves the logs for you. - Try `nvr logs` or `docker logs moonfire-nvr`. + `moonfire-nvr config 2>debug-log` command, output will be in the + `debug-log` file. * When running through systemd, stderr will be redirected to the journal. Try `sudo journalctl --unit moonfire-nvr` to view the logs. You also likely want to set `MOONFIRE_FORMAT=systemd` to format logs as @@ -56,8 +54,6 @@ Logging options are controlled by environment variables: consumption. * Errors include a backtrace if `RUST_BACKTRACE=1` is set. -If you use Docker, set these via Docker's `--env` argument. - With `MOONFIRE_FORMAT` left unset, log events look as follows: ```text @@ -191,23 +187,6 @@ quickly enough. In the latter case, you'll likely see a ### Server errors -#### `clock_gettime failed: EPERM: Operation not permitted` - -If commands fail with an error like the following, you're likely running -Docker with an overly restrictive `seccomp` setup. [This stackoverflow -answer](https://askubuntu.com/questions/1263284/apt-update-throws-signature-error-in-ubuntu-20-04-container-on-arm/1264921#1264921) describes the -problem in more detail. The simplest solution is to add -`--security-opt=seccomp:unconfined` to your Docker commandline. -If you are using the recommended `/usr/local/bin/nvr` wrapper script, -add this option to the `common_docker_run_args` section. - -```console -$ sudo docker run --rm -it moonfire-nvr:latest -clock_gettime failed: EPERM: Operation not permitted - -This indicates a broken environment. See the troubleshooting guide. -``` - #### `Error: pts not monotonically increasing; got 26615520 then 26539470` If your streams cut out and you see error messages like this one in Moonfire @@ -322,8 +301,7 @@ mechanism to fix old timestamps after the fact. Ideas and help welcome; see #### `moonfire-nvr config` displays garbage -This happens if you're not using the premade Docker containers and have -configured your machine is configured to a non-UTF-8 locale, due to +This may happen if your machine is configured to a non-UTF-8 locale, due to gyscos/Cursive#13. As a workaround, try setting the environment variable `LC_ALL=C.UTF-8`. diff --git a/release.bash b/release.bash deleted file mode 100755 index 9d82c0c8..00000000 --- a/release.bash +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash -# This file is part of Moonfire NVR, a security camera network video recorder. -# Copyright (C) 2021 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt. -# SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception - -# Pushes a release to Docker. See guides/build.md#release-procedure. - -set -o errexit -set -o pipefail -set -o xtrace - -set_latest() { - # Our images are manifest lists (for multiple architectures). - # "docker tag" won't copy those. The technique below is adopted from here: - # https://github.com/docker/buildx/issues/459#issuecomment-750011371 - local image="$1" - local hashes=($(docker manifest inspect "${image}:${version}" | - jq --raw-output '.manifests[].digest')) - time docker manifest rm "${image}:latest" || true - time docker manifest create \ - "${image}:latest" \ - "${hashes[@]/#/${image}@}" - time docker manifest push "${image}:latest" -} - -build_and_push() { - local image="$1" - local target="$2" - time docker buildx build \ - --push \ - --tag="${image}:${version}" \ - --target="${target}" \ - --platform=linux/amd64,linux/arm64/v8,linux/arm/v7 \ - -f docker/Dockerfile . -} - -version="$(git describe --dirty)" -if [[ ! "${version}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Expected a vX.Y.Z version tag, got ${version}." >&2 - exit 1 -fi - -if [[ -n "$(git status --porcelain)" ]]; then - echo "git status says there's extra stuff in this directory." >&2 - exit 1 -fi - -build_and_push scottlamb/moonfire-nvr deploy -build_and_push scottlamb/moonfire-dev dev -set_latest scottlamb/moonfire-nvr -set_latest scottlamb/moonfire-dev diff --git a/server/Cargo.lock b/server/Cargo.lock index d1c369d4..79d985b6 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -303,10 +303,8 @@ dependencies = [ "lazy_static", "libc", "log", - "maplit", - "ncurses", "signal-hook", - "term_size", + "termion", "unicode-segmentation", "unicode-width", ] @@ -982,12 +980,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "matchers" version = "0.1.0" @@ -1198,17 +1190,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a1fe2275b68991faded2c80aa4a33dba398b77d276038b8f50701a22e55918" -[[package]] -name = "ncurses" -version = "5.101.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "nix" version = "0.26.1" @@ -1338,6 +1319,12 @@ dependencies = [ "libc", ] +[[package]] +name = "numtoa" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" + [[package]] name = "odds" version = "0.4.0" @@ -1581,6 +1568,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_termios" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" +dependencies = [ + "redox_syscall", +] + [[package]] name = "reffers" version = "0.7.0" @@ -1994,13 +1990,15 @@ dependencies = [ ] [[package]] -name = "term_size" -version = "0.3.2" +name = "termion" +version = "1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" dependencies = [ "libc", - "winapi", + "numtoa", + "redox_syscall", + "redox_termios", ] [[package]] diff --git a/server/Cargo.toml b/server/Cargo.toml index e9e78523..90094774 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -35,7 +35,7 @@ bpaf = { version = "0.9.1", features = ["autocomplete", "bright-color", "derive" bytes = "1" byteorder = "1.0" chrono = "0.4.23" -cursive = "0.20.0" +cursive = { version = "0.20.0", default-features = false, features = ["termion-backend"] } db = { package = "moonfire-db", path = "db" } futures = "0.3" fnv = "1.0" diff --git a/server/Cross.toml b/server/Cross.toml new file mode 100644 index 00000000..7f18f4be --- /dev/null +++ b/server/Cross.toml @@ -0,0 +1,14 @@ +[build.env] +volumes = [ + # For the (optional) `bundled-ui` feature. + "UI_BUILD_DIR", + + # For tests which use the `America/Los_Angeles` zone. + "ZONEINFO=/usr/share/zoneinfo", +] + +passthrough = [ + # Cross's default docker image doesn't install `git`, so `git_version!` doesn't work. + # Allow passing through the version via this environment variable. + "VERSION", +] diff --git a/server/build.rs b/server/build.rs index 9073d779..885aaacb 100644 --- a/server/build.rs +++ b/server/build.rs @@ -6,13 +6,17 @@ use std::fmt::Write; use std::path::{Path, PathBuf}; -use std::process::ExitCode; +use std::process::Command; -const UI_DIR: &str = "../ui/build"; +const UI_BUILD_DIR_ENV_VAR: &str = "UI_BUILD_DIR"; +const DEFAULT_UI_BUILD_DIR: &str = "../ui/build"; + +type BoxError = Box; fn ensure_link(original: &Path, link: &Path) { match std::fs::read_link(link) { Ok(dst) if dst == original => return, + Ok(_) => std::fs::remove_file(link).expect("removing stale symlink should succeed"), Err(e) if e.kind() != std::io::ErrorKind::NotFound => { panic!("couldn't create link {link:?} to original path {original:?}: {e}") } @@ -67,37 +71,30 @@ fn stringify_files(files: &FileMap) -> Result { Ok(buf) } -fn main() -> ExitCode { - // Explicitly declare dependencies, so this doesn't re-run if other source files change. - println!("cargo:rerun-if-changed=build.rs"); - +fn handle_bundled_ui() -> Result<(), BoxError> { // Nothing to do if the feature is off. cargo will re-run if features change. if !cfg!(feature = "bundled-ui") { - return ExitCode::SUCCESS; + return Ok(()); } + let ui_dir = + std::env::var(UI_BUILD_DIR_ENV_VAR).unwrap_or_else(|_| DEFAULT_UI_BUILD_DIR.to_owned()); + // If the feature is on, also re-run if the actual UI files change. - println!("cargo:rerun-if-changed={UI_DIR}"); + println!("cargo:rerun-if-env-changed={UI_BUILD_DIR_ENV_VAR}"); + println!("cargo:rerun-if-changed={ui_dir}"); let out_dir: PathBuf = std::env::var_os("OUT_DIR") .expect("cargo should set OUT_DIR") .into(); - let abs_ui_dir = std::fs::canonicalize(UI_DIR) - .expect("ui dir should be accessible. Did you run `npm run build` first?"); + let abs_ui_dir = std::fs::canonicalize(&ui_dir).map_err(|e| format!("ui dir {ui_dir:?} should be accessible. Did you run `npm run build` first?\n\ncaused by:\n{e}"))?; let mut files = FileMap::default(); for entry in walkdir::WalkDir::new(&abs_ui_dir) { - let entry = match entry { - Ok(e) => e, - Err(e) => { - eprintln!( - "walkdir failed. Did you run `npm run build` first?\n\n\ - caused by:\n{e}" - ); - return ExitCode::FAILURE; - } - }; + let entry = entry.map_err(|e| { + format!("walkdir failed. Did you run `npm run build` first?\n\ncaused by:\n{e}") + })?; if !entry.file_type().is_file() { continue; } @@ -135,6 +132,13 @@ fn main() -> ExitCode { ); } + if !files.contains_key("index.html") { + return Err(format!( + "No `index.html` within {ui_dir:?}. Did you run `npm run build` first?" + ) + .into()); + } + let files = stringify_files(&files).expect("write to String should succeed"); let mut out_rs_path = std::path::PathBuf::new(); out_rs_path.push(&out_dir); @@ -145,5 +149,49 @@ fn main() -> ExitCode { out_link_path.push(&out_dir); out_link_path.push("ui_files"); ensure_link(&abs_ui_dir, &out_link_path); - return ExitCode::SUCCESS; + Ok(()) +} + +fn handle_version() -> Result<(), BoxError> { + println!("cargo:rerun-if-env-changed=VERSION"); + if std::env::var("VERSION").is_ok() { + return Ok(()); + } + + // Get version from `git describe`. Inspired by the `git-version` crate. + // We don't use that directly because `cross`'s default docker image doesn't install `git`, + // and thus we need the environment variable pass-through above. + + // Avoid reruns when the output doesn't meaningfully change. I don't think this is quite right: + // it won't recognize toggling between `-dirty` and not. But it'll do. + let dir = Command::new("git") + .arg("rev-parse") + .arg("--git-dir") + .output()? + .stdout; + let dir = String::from_utf8(dir).unwrap(); + let dir = dir.strip_suffix('\n').unwrap(); + println!("cargo:rerun-if-changed={dir}/logs/HEAD"); + println!("cargo:rerun-if-changed={dir}/index"); + + // Plumb the version through. + let version = Command::new("git") + .arg("describe") + .arg("--always") + .arg("--dirty") + .output()? + .stdout; + let version = String::from_utf8(version).unwrap(); + let version = version.strip_suffix('\n').unwrap(); + println!("cargo:rustc-env=VERSION={version}"); + + Ok(()) +} + +fn main() -> Result<(), BoxError> { + // Explicitly declare dependencies, so this doesn't re-run if other source files change. + println!("cargo:rerun-if-changed=build.rs"); + handle_bundled_ui()?; + handle_version()?; + Ok(()) } diff --git a/server/src/bundled_ui.rs b/server/src/bundled_ui.rs index 7697fdea..2d42de5b 100644 --- a/server/src/bundled_ui.rs +++ b/server/src/bundled_ui.rs @@ -22,6 +22,7 @@ struct BuildFile { encoding: FileEncoding, } +#[allow(unused)] // it's valid for a UI to have all uncompressed files or vice versa. #[derive(Copy, Clone)] enum FileEncoding { Uncompressed, diff --git a/server/src/main.rs b/server/src/main.rs index 13ae1f81..b41d6ccb 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -25,7 +25,8 @@ mod bundled_ui; const DEFAULT_DB_DIR: &str = "/var/lib/moonfire-nvr/db"; -const VERSION: &str = git_version::git_version!(args = ["--always", "--dirty"]); +// This is either in the environment when `cargo` is invoked or set from within `build.rs`. +const VERSION: &str = env!("VERSION"); /// Moonfire NVR: security camera network video recorder. #[derive(Bpaf, Debug)]