Replace Zoneminder with AgentDVR (iSpy).
authorMatt Birkholz <matt@birchwood-abbey.net>
Wed, 18 Sep 2024 16:44:20 +0000 (10:44 -0600)
committerMatt Birkholz <matt@birchwood-abbey.net>
Wed, 18 Sep 2024 16:44:20 +0000 (10:44 -0600)
README.org
playbooks/timezone.yml
private_ex/vars-abbey.yml
roles_t/abbey-dvr/tasks/main.yml

index 00d5405799dadf7fe846aa745402974b1ee9b97a..e08fdd96c8a7641a1e232031b3679eeec76a2b50 100644 (file)
@@ -2187,352 +2187,251 @@ The ~ExecStartPre=/bin/sleep 30~ is intended to avoid recent hangs in
 
 * The Abbey DVR Role
 
-The abbey uses Zoneminder to record video from PoE IP HD security
-cameras.  The Abbey DVR Role installs Zoneminder and configures it to
-record to =/Zoneminder/=, the mount point for a separate, large
-storage volume.  It follows the instructions in Zoneminder's
-=README.Debian= (in =/usr/share/doc/zoneminder/=) to create the ~zm~
-database and configure Apache.
+The abbey uses AgentDVR to record video from PoE IP HD security
+cameras.  The "download" button on iSpy's Download page
+([[https://www.ispyconnect.com/download]]), when "Agent DVR - Linux/
+macOS/ RPi" is chosen, suggests the following command lines (the
+second of which is broken across three lines).
 
-** DVR Machine Setup
-
-The installation process involves some manual intervention.  The first
-time a host is enrolled, Ansible will install the necessary packages,
-but it cannot create the database, nor the database user (yet, in the
-first pass).  After adding the new machine to the ~dvrs~ group in
-[[=hosts=]], run Ansible to get the Zoneminder software installed.
-
-: ./abbey config HOST
-
-Several configuration steps will be skipped because =/Zoneminder/= has
-not been created yet.  To proceed, first create the database and
-database user manually, as described in section [[*Manually Create Zoneminder DB and User][Manually Create
-Zoneminder DB and User]].
-
-** Create =/Zoneminder/=
-
-=/Zoneminder/= should be a separate, large volume lest Zoneminder fill
-the root file system.  For acceptable performance, =/Zoneminder/=
-should also be the mount point of a solid-state disk (SSD).  A
-symbolic link at =/var/cache/zoneminder/events= targets =/Zoneminder=
-to make it Zoneminder's "default" storage area.  (The ~PurgeWhenFull~
-filter only works with the default storage area in v1.34.)
-
-** Continue Zoneminder Configuration
-
-Once the ~zm~ database (and ~zmuser~ database user) are created, and a
-large volume mounted at =/Zoneminder/=, Ansible can continue with the
-Zoneminder configuration.
-
-: ./abbey configure HOST
-
-Configuring Zoneminder's cameras is still a manual process as
-described in the final section, [[*Configure Cameras][Configure Cameras]], below.
-
-** Include Abbey Variables
+#+BEGIN_SRC sh
+sudo apt-get install curl
+bash <(curl -s "https://raw.githubusercontent.com/\
+ispysoftware/agent-install-scripts/main/v2/\
+install.sh")
+#+END_SRC
 
-Private variables in =private/vars-abbey.yml= are needed, and included
-here, as in the ~abbey-core~ role.  The file path is relative to the
-playbook's directory, =playbooks/=.
+Ansible assists by creating the system user ~agentdvr~ and granting it
+enough ~sudo~ latitude to run the installer as instructed above.
+Though a system user, the account gets a home directory,
+=/home/agentdvr/= in which to do the installation.  The rest of the
+DVR role, "phase two", waits until AgentDVR is installed.
 
-#+CAPTION: [[file:roles_t/abbey-dvr/tasks/main.yml][=roles_t/abbey-dvr/tasks/main.yml=]]
-#+BEGIN_SRC conf :tangle roles_t/abbey-dvr/tasks/main.yml :mkdirp yes
----
-- name: Include private abbey variables.
-  include_vars: ../private/vars-abbey.yml
-#+END_SRC
+AgentDVR is installed, after Ansible has set things up, by running the
+command lines prescribed by iSpy while logged in as ~agentdvr~ with
+the current default directory =/home/agentdvr/=.  The installer should
+create the =/home/agentdvr/AgentDVR/= directory.  Its offer to install
+a system service is declined.
 
-** Install Zoneminder v1.34
+After AgentDVR is installed, when the =/home/agentdvr/AgentDVR/=
+directory exists, Ansible is run again to install the system service.
 
-The latest version of Zoneminder (1.36) was manually downloaded, built
-and installed, but it immediately had problems, randomly producing
-short events, dropping "problem" cameras entirely, etc.  Version 1.34
-did not have those problems, but could still melt down (thrash?) when
-=/Zoneminder/= was a Seagate Barracuda in a USB3.1gen2 external drive
-enclosure.  A Western Digital Passport Ultra seemed to work much
-better, for a short while.  Ultimately a solid-state drive (a 2TB
-USB3.2 Gen2 Samsung T7 Shield) mounted at =/Zoneminder/= got
-Zoneminder 1.34 to work reliably.
+** Create User ~agentdvr~
 
-After uninstalling 1.36, the Debian 11 package (1.34) was installed
-and configured per the instructions in sections "Web server set-up"
-and "Time Zone" in =/usr/share/doc/zoneminder/README.Debian.gz=.
+AgentDVR runs as the system user ~agentdvr~, which is created here.
 
 #+CAPTION: [[file:roles_t/abbey-dvr/tasks/main.yml][=roles_t/abbey-dvr/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/abbey-dvr/tasks/main.yml
-
-- name: Install Zoneminder.
-  become: yes
-  apt: pkg=zoneminder
-
-- name: Enable Apache modules for Zoneminder.
-  become: yes
-  apache2_module:
-    name: "{{ item }}"
-  loop: [ cgid, rewrite, expires, headers ]
-  notify: Restart Apache2.
-
-- name: Enable Zoneminder Apache configuration.
-  become: yes
-  command:
-    cmd: a2enconf zoneminder
-    creates: /etc/apache2/conf-enabled/zoneminder.conf
-  notify: Restart Apache2.
-
-- name: Configure MySQL for Zoneminder.
+---
+- name: Create agentdvr.
   become: yes
-  copy:
-    content: |
-      [mysqld]
-      sql_mode = NO_ENGINE_SUBSTITUTION
-    dest: /etc/mysql/conf.d/zoneminder.cnf
-  notify: Restart MySQL.
+  user:
+    name: agentdvr
+    system: yes
+    home: /home/agentdvr
+    shell: /bin/bash
+    append: yes
+    groups: video
 
-- name: Configure PHP date.timezone.
+- name: Add {{ ansible_user }} to agentdvr group.
   become: yes
-  lineinfile:
-    regexp: date.timezone ?=
-    line: date.timezone = {{ lookup('file', '/etc/timezone') }}
-    path: "{{ item }}"
-  loop:
-  - /etc/php/8.2/cli/php.ini
-  - /etc/php/8.2/apache2/php.ini
-  notify: Restart Apache2.
+  user:
+    name: "{{ ansible_user }}"
+    append: yes
+    groups: agentdvr
 
-- name: Enable/Start Apache2.
+- name: Create /home/agentdvr/.
   become: yes
-  systemd:
-    service: apache2
-    enabled: yes
-    state: started
+  file:
+    path: /home/agentdvr
+    state: directory
+    owner: agentdvr
+    group: agentdvr
+    mode: u=rwx,g=rwx,o=rx
 #+END_SRC
 
-#+CAPTION: [[file:roles_t/abbey-dvr/handlers/main.yml][=roles_t/abbey-dvr/handlers/main.yml=]]
-#+BEGIN_SRC conf :tangle roles_t/abbey-dvr/handlers/main.yml :mkdirp yes
----
-- name: Restart MySQL.
-  become: yes
-  systemd:
-    service: mysql
-    state: restarted
+** Authorize User ~agentdvr~
 
-- name: Restart Apache2.
-  become: yes
-  systemd:
-    service: apache2
-    state: restarted
-#+END_SRC
-
-The following Rsyslog configuration drop-in gets Zoneminder's natter
-out of =/var/log/syslog=.
+The AgentDVR installer is also run by ~agentdvr~, which is authorized
+to run a handful of system commands.  This small set is sufficient
+/if/ the offer to create the system service is declined.  In that
+case, the installer will run the program in the terminal.
 
 #+CAPTION: [[file:roles_t/abbey-dvr/tasks/main.yml][=roles_t/abbey-dvr/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/abbey-dvr/tasks/main.yml
 
-- name: Use /var/log/zoneminder.log
-  become: yes
+- name: Authorized agentdvr.
   copy:
     content: |
-      :programname,startswith,"zm" -/var/log/zoneminder.log
-      & stop
-    dest: /etc/rsyslog.d/40-zoneminder.conf
+      ALL ALL=(agentdvr) NOPASSWD: /bin/systemctl,/bin/apt-get,\
+          /sbin/adduser,/sbin/usermod
+    dest: /etc/sudoers.d/agentdvr
 #+END_SRC
 
-** Create Zoneminder Database
+** Test For =AgentDVR/=
 
-Zoneminder's MariaDB database is created by the following task, when
-the ~mysql_db~ Ansible module supports ~check_implicit_admin~.
+The following task probes for the =/home/agentdvr/AgentDVR/=
+directory, to detect that the build/install process has completed.  It
+registers the results in the ~agentdvr~ variable.  Several of the
+remaining installation steps are skipped unless
+~agentdvr.stat.exists~.
 
-#+BEGIN_SRC conf
+#+CAPTION: [[file:roles_t/abbey-dvr/tasks/main.yml][=roles_t/abbey-dvr/tasks/main.yml=]]
+#+BEGIN_SRC conf :tangle roles_t/abbey-dvr/tasks/main.yml
 
-- name: Create Zoneminder DB.
-  become: yes
-  mysql_db:
-    check_implicit_admin: yes
-    name: zm
-    collation: utf8mb4_general_ci
-    encoding: utf8mb4
+- name: Test for AgentDVR directory.
+  stat:
+    path: /home/agentdvr/AgentDVR
+  register: agentdvr
+- debug:
+    msg: "/home/agentdvr/AgentDVR/ does not yet exist"
+  when: not agentdvr.stat.exists
 #+END_SRC
 
-Unfortunately it does not currently, yet the institute prefers the
-more secure Unix socket authentication method.  Rather than create a
-privileged DB user, the ~zm~ database is created manually (below).
+** Create AgentDVR Service
 
-** Create Zoneminder DB User
+This service definition came from the template downloaded (from [[https://raw.githubusercontent.com/ispysoftware/agent-install-scripts/main/v2/AgentDVR.service][here]])
+by the installer, specifically the =linux_setup2.sh= script downloaded
+by =install.sh=.
 
-The following task would create the DB user (~mysql_user~ supports
-~check_implicit_admin~) /but/ the ~zm~ database was not created above.
-
-The DB user's password is taken from the ~zoneminder_dbpass~
-variable, kept in =private/vars-abbey.yml=, and generated e.g. with
-the ~apg -n 1 -x 12 -m 12~ command.
+#+CAPTION: [[file:roles_t/abbey-dvr/tasks/main.yml][=roles_t/abbey-dvr/tasks/main.yml=]]
+#+BEGIN_SRC conf :tangle roles_t/abbey-dvr/tasks/main.yml
 
-#+CAPTION: [[file:private_ex/vars-abbey.yml][=private_ex/vars-abbey.yml=]]
-#+BEGIN_SRC conf :tangle private_ex/vars-abbey.yml
-zoneminder_dbpass:           gakJopbikJadsEdd
-#+END_SRC
+  - name: Install AgentDVR.service.
+    become: yes
+    copy:
+      content: |
+        [Unit]
+        Description=AgentDVR
 
-#+BEGIN_SRC conf
+        [Service]
+        WorkingDirectory=/home/agentdvr/AgentDVR
+        ExecStart=/home/agentdvr/AgentDVR/Agent
 
-- name: Create Zoneminder DB user.
-  become: yes
-  mysql_user:
-    check_implicit_admin: yes
-    name: zmuser
-    password: "{{ zoneminder_dbpass }}"
-    priv: >-
-      zm.*:
-      lock tables,alter,create,index,select,insert,update,delete
-#+END_SRC
+        # fix memory management issue with dotnet core
+        Environment="MALLOC_TRIM_THRESHOLD_=100000"
 
-** Manually Create Zoneminder DB and User
+        # to query logs using journalctl, set a logical name here
+        SyslogIdentifier=AgentDVR
 
-The Zoneminder database and database user are created manually with
-the following SQL (with the ~zoneminder_dbpass~ spliced in).  The SQL
-commands are entered at the SQL prompt of the ~sudo mysql~ command, or
-perhaps piped into the command.
+        User=agentdvr
 
-#+BEGIN_SRC sql
-create database zm
-    character set utf8mb4
-    collate utf8mb4_general_ci;
-grant lock tables,alter,create,index,select,insert,update,delete
-    on zm.*
-    to 'zmuser'@'localhost'
-    identified by '{{ zoneminder_dbpass }}';
-flush privileges;
-exit;
-#+END_SRC
+        # ensure the service automatically restarts
+        Restart=always
+        # amount of time to wait before restarting the service
+        RestartSec=5
 
-Finally, ~zm~'s tables are created, completing the database setup,
+        [Install]
+        WantedBy=multi-user.target
+      dest: /etc/systemd/system/AgentDVR.service
+    when: agentdvr.stat.exists
 
-#+BEGIN_SRC sh
-sudo mysql < /usr/share/zoneminder/db/zm_create.sql
+  - name: Enable/Start AgentDVR.service.
+    become: yes
+    systemd:
+      service: AgentDVR
+      enabled: yes
+      state: started
+    when: agentdvr.stat.exists
 #+END_SRC
 
-** Use =/Zoneminder/=
+** Create AgentDVR Storage
 
-The following tasks start with a test for the existence of
-=/Zoneminder=.  Configuration tasks that require =/Zoneminder/= or the
-~zm~ database are executed only when ~zoneminder.stat.exists~.  The
-last "Link..." task below "forces" the link, whether the target exists
-or not (yet).
+The abbey uses a separate volume to store surveillance recordings,
+lest the DVR program fill the root file system.  The volume is mounted
+at =/DVR/=.  The following tasks create =/DVR/AgentDVR/video/=
+(whether a large volume is mounted there /or not/!) with appropriate
+permissions so that the instructions for configuring a default storage
+location do not fail.
 
 #+CAPTION: [[file:roles_t/abbey-dvr/tasks/main.yml][=roles_t/abbey-dvr/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/abbey-dvr/tasks/main.yml
 
-- name: Test for /Zoneminder/.
-  stat:
-    path: /Zoneminder
-  register: zoneminder
-- debug:
-    msg: "/Zoneminder/ does not yet exist"
-  when: not zoneminder.stat.exists
-
-- name: Check /Zoneminder/.
+- name: Create /DVR/AgentDVR/.
   become: yes
   file:
     state: directory
-    path: /Zoneminder
-    owner: www-data
-    group: www-data
-    mode: u=rwx,g=rx,o=rx
-  when: zoneminder.stat.exists
+    path: /DVR/AgentDVR
+    owner: agentdvr
+    group: agentdvr
+    mode: u=rwx,g=rwx,o=
 
-- name: Link to /Zoneminder/.
+- name: Create /DVR/AgentDVR/video/.
   become: yes
   file:
-    state: link
-    src: /Zoneminder
-    path: /var/cache/zoneminder/events
-    force: yes
-    follow: no
+    state: directory
+    path: /DVR/AgentDVR/video
+    owner: agentdvr
+    group: agentdvr
+    mode: u=rwx,g=rx,o=
 #+END_SRC
 
-** Configure Zoneminder
+** Configure IP Cameras
 
-The remaining tasks ensure that the =/etc/zm/zm.conf= file has the
-proper permissions and contains the correct password.
+A new security camera is setup as described in [[*Cloistering][Cloistering]], after
+which the camera should be accessible by name on the abbey networks.
+Assuming ~ping -c1 new~ works, the camera's web interface will be
+accessible at ~http://new/~.
 
-#+CAPTION: [[file:roles_t/abbey-dvr/tasks/main.yml][=roles_t/abbey-dvr/tasks/main.yml=]]
-#+BEGIN_SRC conf :tangle roles_t/abbey-dvr/tasks/main.yml
+The administrator uses this to make the following changes.
 
-- name: Set /etc/zm/zm.conf permissions.
-  become: yes
-  file:
-    path: /etc/zm/zm.conf
-    owner: root
-    group: www-data
-    mode: u=rw,g=r,o=
+  - Set a password on the administrative account.
+  - Create an unprivileged user with a short password,
+    e.g. ~user:blah~.
+  - Set the frame rate to 5fps.  The abbey prefers HD resolution and
+    long duration logs, thus fewer frames per second.
 
-- name: Set Zoneminder passphrase.
-  become: yes
-  lineinfile:
-    regexp: '^ *ZM_DB_PASS *='
-    line: ZM_DB_PASS={{ zoneminder_dbpass }}
-    path: /etc/zm/zm.conf
-#+END_SRC
+** Configure AgentDVR's Cameras
 
-Finally, Zoneminder's service unit can be enabled (and started) /if/
-=/Zoneminder/= exists.  It is assumed that, if =/Zoneminder/= exists,
-the ~zm~ database has also been created, and the service is ready to
-run.
+After Ansible has configured and started the AgentDVR service, its web
+UI will be available at ~http://core:8090/~.  The initial Live View
+will be empty, overlayed with instructions to click the edit button.
 
-#+CAPTION: [[file:roles_t/abbey-dvr/tasks/main.yml][=roles_t/abbey-dvr/tasks/main.yml=]]
-#+BEGIN_SRC conf :tangle roles_t/abbey-dvr/tasks/main.yml
 
-- name: Enable/Start Zoneminder.
-  become: yes
-  systemd:
-    service: zoneminder
-    enabled: yes
-    state: started
-  when: zoneminder.stat.exists
-#+END_SRC
+The wizard will ask for each device's general configuration
+parameters.  The abbey uses SV3C IP cameras with a full HD stream as
+well as a standard definition "vice stream".  AgentDVR wants both.
 
-** Configure Cameras
+  - General:
+    + On: yes
+    + Name: Outside
+    + Source Type: Network Camera
+      - Username: user
+      - Password: blah
+      - Live URL: rtsp://new.birchwood.private:554/12
+      - Record URL: rtsp://new.birchwood.private:554/11
 
-A new security camera is setup as described in [[*Cloistering][Cloistering]], after
-which the camera should be accessible by name on the abbey networks.
-Assuming ~ping -c1 new~ works, the camera's web interface will be
-accessible at ~http://new/~.
+Additional cameras are added via the "New Device" item in the Server
+Menu.  This step is completed when all cameras are streaming to
+AgentDVR's Live View.
+
+** Configure AgentDVR's Default Storage
 
-The abbey's administrator logs into ~http://new/~ and turns off any
-OSD (on-screen display).  Zoneminder will add its own timestamp, for
-the best accuracy and reliability.  The administrator also turns down
-the frame rate to 5fps.  The abbey prefers HD resolution (e.g. 1080p)
-and long duration logs, thus fewer frames per second.  The
-administrator also creates an unprivileged user with a short password
-e.g. ~user:gobbledygook~.
-
-After Ansible has configured and started Zoneminder, a camera can be
-created by clicking on "Add" in the Zoneminder console.  (If the
-Zoneminder host was named "security", the Zoneminder console can be
-found at ~http://security/zm/~.)  In the Add dialog, the following
-settings should be changed.  (The parenthesized settings are default
-settings that should be checked but are probably already correctly
-set.)
-
-  - In the "General" tab, specify:
-    - Name: Front
-    - (Server: None)
-    - (Source type: Ffmpeg)
-    - Function: Record
-    - Enabled: yes
-    - (Analysis FPS: <blank>)
-    - (Maximum FPS: <blank>)
-    - (Alarm Maximum FPS: <blank>)
-  - In the "Source" tab, specify:
-    - Src path: rtsp://user:gobbledygook@new.small.private.:554/11
-    - (Method: TCP)
-    - (Target colorspace: 32 bit colour)
-    - Capture Resolution: 1920x1080 1080p
-  - In the "Timestamp" tab, specify:
-    - Timestamp Label X: 10
-    - Timestamp Label Y: 10
-    - Font Size: Large
-  - In the "Buffers" tab, specify:
-    - Image Buffer Size (frames): 40
+AgentDVR's web interface is also used to configure a default storage
+location.  From the Server Menu (upper left), the administrator chooses
+Configuration Settings, the Storage tab, the Configure button, and the
+add (plus) button.  The storage location is set to =/DVR/AgentDVR/=
+and the "default" toggle is set.  Several OK buttons then need to be
+pressed before the task is complete.
+
+** Configure AgentDVR's Recordings
+
+After a default storage location has been configured, AgentDVR's
+cameras can begin recording.  The "Edit Devices" dialog lists (via the
+"Edit Devices" item in the Server Menu) the configured cameras.  The
+edit buttons lead to the device settings where the following
+parameters are set (in the Recording and Storage tabs).
+
+  - Recording:
+    + Mode: Constant
+    + Encoder: Raw Record Stream
+    + Max record time: 900 (15 minutes)
+  - Storage:
+    + Location: /DVR/AgentDVR/
+    + Folder: Outside
+    + Storage Management:
+      - On: yes
+      - Max Size: 0 (unlimited)
+      - Max Age: 168 (7 days)
 
 
 * The Abbey TVR Role
@@ -3656,12 +3555,11 @@ if ($ARGV[0] eq "tz") {
 
 - hosts: dvrs
   tasks:
-  - name: Restart Zoneminder.
+  - name: Restart AgentDVR.
     become: yes
     systemd:
-      service: "{{ item }}"
+      service: AgentDVR
       state: restarted
-    loop: [ mysql, zoneminder ]
     when: new_tz.changed
 
 - hosts: tvrs
index 3783cbd937d867f86d613182f0b0de99c3d907ab..de065120e1dacbd6a2e7cc06cfb7d900400064bf 100644 (file)
@@ -9,12 +9,11 @@
 
 - hosts: dvrs
   tasks:
-  - name: Restart Zoneminder.
+  - name: Restart AgentDVR.
     become: yes
     systemd:
-      service: "{{ item }}"
+      service: AgentDVR
       state: restarted
-    loop: [ mysql, zoneminder ]
     when: new_tz.changed
 
 - hosts: tvrs
index fbd328e5d3b0b2fe57f8217c108944af06bb5e53..f8969276b855c0fbd15ca7d9f982b7734f25f3d7 100644 (file)
@@ -3,6 +3,4 @@ kamino_addr:                192.168.56.14
 kessel_addr:                10.84.138.8
 ord_mantell_addr:           10.84.138.10
 
-zoneminder_dbpass:           gakJopbikJadsEdd
-
 mythtv_dbpass:           daJkibpoJkag
index 40bbde2951a7507e8a505c2eba69579a240ab5e4..17b0824834d5c4c7aa9ea5bbb7384b9d911831be 100644 (file)
 ---
-- name: Include private abbey variables.
-  include_vars: ../private/vars-abbey.yml
-
-- name: Install Zoneminder.
+- name: Create agentdvr.
   become: yes
-  apt: pkg=zoneminder
+  user:
+    name: agentdvr
+    system: yes
+    home: /home/agentdvr
+    shell: /bin/bash
+    append: yes
+    groups: video
 
-- name: Enable Apache modules for Zoneminder.
+- name: Add {{ ansible_user }} to agentdvr group.
   become: yes
-  apache2_module:
-    name: "{{ item }}"
-  loop: [ cgid, rewrite, expires, headers ]
-  notify: Restart Apache2.
+  user:
+    name: "{{ ansible_user }}"
+    append: yes
+    groups: agentdvr
 
-- name: Enable Zoneminder Apache configuration.
+- name: Create /home/agentdvr/.
   become: yes
-  command:
-    cmd: a2enconf zoneminder
-    creates: /etc/apache2/conf-enabled/zoneminder.conf
-  notify: Restart Apache2.
+  file:
+    path: /home/agentdvr
+    state: directory
+    owner: agentdvr
+    group: agentdvr
+    mode: u=rwx,g=rwx,o=rx
 
-- name: Configure MySQL for Zoneminder.
-  become: yes
+- name: Authorized agentdvr.
   copy:
     content: |
-      [mysqld]
-      sql_mode = NO_ENGINE_SUBSTITUTION
-    dest: /etc/mysql/conf.d/zoneminder.cnf
-  notify: Restart MySQL.
+      ALL ALL=(agentdvr) NOPASSWD: /bin/systemctl,/bin/apt-get,\
+          /sbin/adduser,/sbin/usermod
+    dest: /etc/sudoers.d/agentdvr
 
-- name: Configure PHP date.timezone.
-  become: yes
-  lineinfile:
-    regexp: date.timezone ?=
-    line: date.timezone = {{ lookup('file', '/etc/timezone') }}
-    path: "{{ item }}"
-  loop:
-  - /etc/php/8.2/cli/php.ini
-  - /etc/php/8.2/apache2/php.ini
-  notify: Restart Apache2.
-
-- name: Enable/Start Apache2.
-  become: yes
-  systemd:
-    service: apache2
-    enabled: yes
-    state: started
+- name: Test for AgentDVR directory.
+  stat:
+    path: /home/agentdvr/AgentDVR
+  register: agentdvr
+- debug:
+    msg: "/home/agentdvr/AgentDVR/ does not yet exist"
+  when: not agentdvr.stat.exists
 
-- name: Use /var/log/zoneminder.log
+- name: Install AgentDVR.service.
   become: yes
   copy:
     content: |
-      :programname,startswith,"zm" -/var/log/zoneminder.log
-      & stop
-    dest: /etc/rsyslog.d/40-zoneminder.conf
+      [Unit]
+      Description=AgentDVR
 
-- name: Test for /Zoneminder/.
-  stat:
-    path: /Zoneminder
-  register: zoneminder
-- debug:
-    msg: "/Zoneminder/ does not yet exist"
-  when: not zoneminder.stat.exists
+      [Service]
+      WorkingDirectory=/home/agentdvr/AgentDVR
+      ExecStart=/home/agentdvr/AgentDVR/Agent
 
-- name: Check /Zoneminder/.
-  become: yes
-  file:
-    state: directory
-    path: /Zoneminder
-    owner: www-data
-    group: www-data
-    mode: u=rwx,g=rx,o=rx
-  when: zoneminder.stat.exists
+      # fix memory management issue with dotnet core
+      Environment="MALLOC_TRIM_THRESHOLD_=100000"
 
-- name: Link to /Zoneminder/.
-  become: yes
-  file:
-    state: link
-    src: /Zoneminder
-    path: /var/cache/zoneminder/events
-    force: yes
-    follow: no
+      # to query logs using journalctl, set a logical name here
+      SyslogIdentifier=AgentDVR
 
-- name: Set /etc/zm/zm.conf permissions.
-  become: yes
-  file:
-    path: /etc/zm/zm.conf
-    owner: root
-    group: www-data
-    mode: u=rw,g=r,o=
+      User=agentdvr
 
-- name: Set Zoneminder passphrase.
-  become: yes
-  lineinfile:
-    regexp: '^ *ZM_DB_PASS *='
-    line: ZM_DB_PASS={{ zoneminder_dbpass }}
-    path: /etc/zm/zm.conf
+      # ensure the service automatically restarts
+      Restart=always
+      # amount of time to wait before restarting the service
+      RestartSec=5
 
-- name: Enable/Start Zoneminder.
+      [Install]
+      WantedBy=multi-user.target
+    dest: /etc/systemd/system/AgentDVR.service
+  when: agentdvr.stat.exists
+
+- name: Enable/Start AgentDVR.service.
   become: yes
   systemd:
-    service: zoneminder
+    service: AgentDVR
     enabled: yes
     state: started
-  when: zoneminder.stat.exists
+  when: agentdvr.stat.exists
+
+- name: Create /DVR/AgentDVR/.
+  become: yes
+  file:
+    state: directory
+    path: /DVR/AgentDVR
+    owner: agentdvr
+    group: agentdvr
+    mode: u=rwx,g=rwx,o=
+
+- name: Create /DVR/AgentDVR/video/.
+  become: yes
+  file:
+    state: directory
+    path: /DVR/AgentDVR/video
+    owner: agentdvr
+    group: agentdvr
+    mode: u=rwx,g=rx,o=