Update README.html.
authorMatt Birkholz <matt@birchwood-abbey.net>
Fri, 20 Sep 2024 19:28:32 +0000 (13:28 -0600)
committerMatt Birkholz <matt@birchwood-abbey.net>
Fri, 20 Sep 2024 19:28:32 +0000 (13:28 -0600)
README.html

index e7eabb9072a5ef8d6b70ed7167fb25d585bdc93d..2def3478dfac90e479387b869ff05e79fa49c82f 100644 (file)
@@ -3,7 +3,7 @@
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
 <head>
-<!-- 2024-09-03 Tue 08:46 -->
+<!-- 2024-09-20 Fri 13:28 -->
 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title>Birchwood Abbey Networks</title>
@@ -64,7 +64,7 @@ map is very similar, with differences mainly in terminology,
 philosophy, attitude.
 </p>
 
-<pre class="example" id="org666ada7">
+<pre class="example" id="orgff81049">
                 |                                                   
                 =                                                   
               _|||_                                                 
@@ -144,8 +144,8 @@ with Apache2, spooling email with Postfix and serving it with
 Dovecot-IMAPd, and hosting a VPN with OpenVPN.
 </p>
 </div>
-<div id="outline-container-orgbdc000c" class="outline-3">
-<h3 id="orgbdc000c"><span class="section-number-3">3.1.</span> Install Emacs</h3>
+<div id="outline-container-org5cff3f5" class="outline-3">
+<h3 id="org5cff3f5"><span class="section-number-3">3.1.</span> Install Emacs</h3>
 <div class="outline-text-3" id="text-3-1">
 <p>
 The monks of the abbey are masters of the staff (bo) and Emacs.
@@ -711,7 +711,7 @@ certificate is a terminal session affair (with prompts and lines
 entered as shown below).
 </p>
 
-<pre class="example" id="org39bc164">
+<pre class="example" id="orgc807cc9">
 $ sudo apt install python3-certbot-apache
 $ sudo certbot --apache -d birchwood-abbey.net
 ...
@@ -930,8 +930,8 @@ with Postfix and Dovecot, and providing essential localnet services:
 NTP, DNS and DHCP.
 </p>
 </div>
-<div id="outline-container-orgf7f2984" class="outline-3">
-<h3 id="orgf7f2984"><span class="section-number-3">4.1.</span> Include Abbey Variables</h3>
+<div id="outline-container-orge3238f6" class="outline-3">
+<h3 id="orge3238f6"><span class="section-number-3">4.1.</span> Include Abbey Variables</h3>
 <div class="outline-text-3" id="text-4-1">
 <p>
 In this abbey specific document, most abbey particulars are not
@@ -954,12 +954,10 @@ directory, <q>playbooks/</q>.
 <h3 id="org001474c"><span class="section-number-3">4.2.</span> Install Additional Packages</h3>
 <div class="outline-text-3" id="text-4-2">
 <p>
-The scripts that maintain the abbey's web site and run the Weather
-project use a number of additional software packages.  The
-<q>/WWW/live/Private/make-top-index</q> script uses <code>HTML::TreeBuilder</code> in
-the <code>libhtml-tree-perl</code> package.  The house task list uses JQuery.
-Weather scripts use <code>mit-scheme</code> and <code>gnuplot</code> (in pseudonymous
-packages).
+The scripts that maintain the abbey's web site use a number of
+additional software packages.  The <q>/WWW/live/Private/make-top-index</q>
+script uses <code>HTML::TreeBuilder</code> in the <code>libhtml-tree-perl</code> package.
+The house task list uses JQuery.
 </p>
 
 <div class="org-src-container">
@@ -1129,11 +1127,13 @@ The abbey uses the Apt-Cacher:TNG package cache on Core.  The
 </div>
 </div>
 </div>
-<div id="outline-container-org1a716c3" class="outline-3">
-<h3 id="org1a716c3"><span class="section-number-3">4.8.</span> Use Cloister Apt Cache</h3>
+<div id="outline-container-orga5b3bf9" class="outline-3">
+<h3 id="orga5b3bf9"><span class="section-number-3">4.8.</span> Use Cloister Apt Cache</h3>
 <div class="outline-text-3" id="text-4-8">
 <p>
-Core itself will benefit from using the package cache.
+Core itself will benefit from using the package cache, but should
+contact <code>https</code> repositories directly.  (There are few such cretins
+so caching their packages is not a priority.)
 </p>
 
 <div class="org-src-container">
@@ -1144,6 +1144,8 @@ Core itself will benefit from using the package cache.
     content: &gt;
       Acquire::http::Proxy
       <span class="org-string">"http://apt-cacher.birchwood.private.:3142"</span>;
+
+      Acquire::https::Proxy <span class="org-string">"DIRECT"</span>;
     dest: /etc/apt/apt.conf.d/01proxy
     <span class="org-variable-name">mode: u</span>=rw,g=r,o=r
 </pre>
@@ -1535,10 +1537,9 @@ trends in resource usage.
 <a href="roles_t/abbey-core/tasks/main.yml"><q>roles_t/abbey-core/tasks/main.yml</q></a><pre class="src src-conf">
 - name: Install Munin.
   become: yes
-  apt:
-    pkg: munin
+  <span class="org-variable-name">apt: pkg</span>=munin
 
-- name: Add {{ ansible_user }} to Munin group.
+- name: Add {{ ansible_user }} to munin group.
   become: yes
   user:
     name: <span class="org-string">"{{ ansible_user }}"</span>
@@ -1706,24 +1707,80 @@ Monkey's photo processing scripts use <code>netpbm</code> commands like
 </div>
 </div>
 </div>
-<div id="outline-container-org9a9dc68" class="outline-3">
-<h3 id="org9a9dc68"><span class="section-number-3">4.17.</span> Configure Weather Updates</h3>
+<div id="outline-container-org248a7c3" class="outline-3">
+<h3 id="org248a7c3"><span class="section-number-3">4.17.</span> Install Samba</h3>
 <div class="outline-text-3" id="text-4-17">
 <p>
-Monkey on Core runs <q>/WWW/campus/Weather/Private/cronjob</q> every 5
-minutes and <q>cronjob-midnight</q> at midnight.
+The abbey core provides NAS (Network Attached Storage) service to the
+cloister network.  It also provides writable shares for a Home
+Assistant appliance (Raspberry Pi).
 </p>
 
+<ul class="org-ul">
+<li>Install <code>samba</code>.</li>
+<li>Create system user <code>hass</code>.</li>
+<li>Create <q>/home/hass/{media,backup,share}/</q> with appropriate
+permissions.</li>
+</ul>
+
 <div class="org-src-container">
-<a href="roles_t/abbey-core/tasks/main.yml"><q>roles_t/abbey-core/tasks/main.yml</q></a><pre class="src src-:tangle">
-- name: Create Monkey's weather job.
+<a href="roles_t/abbey-core/tasks/main.yml"><q>roles_t/abbey-core/tasks/main.yml</q></a><pre class="src src-conf">
+- name: Install Samba.
+  become: yes
+  <span class="org-variable-name">apt: pkg</span>=samba
+
+- name: Add system user hass.
+  become: yes
+  user:
+    name: hass
+    system: yes
+
+- name: Add {{ ansible_user }} to hass group.
+  become: yes
+  user:
+    name: <span class="org-string">"{{ ansible_user }}"</span>
+    append: yes
+    groups: hass
+
+- name: Configure shares.
   become: yes
-  cron:
-    name: weather
-    hour: "*"
-    minute: "*/5"
-    job: "[ -d /WWW/house ] &amp;&amp; /WWW/house/Weather/Private/cronjob"
-    user: monkey
+  blockinfile:
+    block: |
+      [<span class="org-type">Shared</span>]
+      <span class="org-variable-name">path</span> = /Shared
+      <span class="org-variable-name">guest ok</span> = yes
+      <span class="org-variable-name">read only</span> = yes
+
+      [<span class="org-type">HASS-backup</span>]
+      <span class="org-variable-name">comment</span> = Home Assistant backup
+      <span class="org-variable-name">path</span> = /home/hass/backup
+      <span class="org-variable-name">valid users</span> = hass
+      <span class="org-variable-name">read only</span> = no
+
+      [<span class="org-type">HASS-media</span>]
+      <span class="org-variable-name">comment</span> = Home Assistant media
+      <span class="org-variable-name">path</span> = /home/hass/media
+      <span class="org-variable-name">valid users</span> = hass
+      <span class="org-variable-name">read only</span> = yes
+
+      [<span class="org-type">HASS-share</span>]
+      <span class="org-variable-name">comment</span> = Home Assistant share
+      <span class="org-variable-name">path</span> = /home/hass/share
+      <span class="org-variable-name">valid users</span> = hass
+      <span class="org-variable-name">read only</span> = no
+    dest: /etc/samba/smb.conf
+    marker: <span class="org-string">"# {mark} ABBEY MANAGED BLOCK"</span>
+  notify: New shares.
+</pre>
+</div>
+
+<div class="org-src-container">
+<a href="roles_t/abbey-core/handlers/main.yml"><q>roles_t/abbey-core/handlers/main.yml</q></a><pre class="src src-conf">
+- name: New shares.
+  become: yes
+  systemd:
+    service: smbd
+    state: reloaded
 </pre>
 </div>
 </div>
@@ -1881,8 +1938,8 @@ hosts never roam, are not associated with a member, and so are
 ./abbey client campus new-host-name
 </pre>
 </div>
-<div id="outline-container-orga5b3bf9" class="outline-3">
-<h3 id="orga5b3bf9"><span class="section-number-3">6.1.</span> Use Cloister Apt Cache</h3>
+<div id="outline-container-orgedd1215" class="outline-3">
+<h3 id="orgedd1215"><span class="section-number-3">6.1.</span> Use Cloister Apt Cache</h3>
 <div class="outline-text-3" id="text-6-1">
 <p>
 The Apt-Cacher:TNG program does not work well on the frontier, so is
@@ -1895,6 +1952,11 @@ Depending on the quality of the Internet connection, this may take a
 while.
 </p>
 
+<p>
+Again, <code>https</code> repositories are contacted directly, cached only on the
+local host.
+</p>
+
 <div class="org-src-container">
 <a href="roles_t/abbey-cloister/tasks/main.yml"><q>roles_t/abbey-cloister/tasks/main.yml</q></a><pre class="src src-conf">---
 - name: Use the local Apt package cache.
@@ -1903,6 +1965,8 @@ while.
     content: &gt;
       Acquire::http::Proxy
       <span class="org-string">"http://apt-cacher.birchwood.private.:3142"</span>;
+
+      Acquire::https::Proxy <span class="org-string">"DIRECT"</span>;
     dest: /etc/apt/apt.conf.d/01proxy
     <span class="org-variable-name">mode: u</span>=rw,g=r,o=r
 </pre>
@@ -1964,10 +2028,9 @@ Each cloistered host is a Munin node.
 <a href="roles_t/abbey-cloister/tasks/main.yml"><q>roles_t/abbey-cloister/tasks/main.yml</q></a><pre class="src src-conf">
 - name: Install Munin Node.
   become: yes
-  apt:
-    pkg: munin-node
+  <span class="org-variable-name">apt: pkg</span>=munin-node
 
-- name: Add {{ ansible_user }} to Munin group.
+- name: Add {{ ansible_user }} to munin group.
   become: yes
   user:
     name: <span class="org-string">"{{ ansible_user }}"</span>
@@ -1998,8 +2061,8 @@ them.
 </div>
 </div>
 </div>
-<div id="outline-container-org5cff3f5" class="outline-3">
-<h3 id="org5cff3f5"><span class="section-number-3">6.4.</span> Install Emacs</h3>
+<div id="outline-container-org832124f" class="outline-3">
+<h3 id="org832124f"><span class="section-number-3">6.4.</span> Install Emacs</h3>
 <div class="outline-text-3" id="text-6-4">
 <p>
 The monks of the abbey are masters of the staff and Emacs.
@@ -2019,852 +2082,342 @@ The monks of the abbey are masters of the staff and Emacs.
 <h2 id="org7341dda"><span class="section-number-2">7.</span> The Abbey Weather Role</h2>
 <div class="outline-text-2" id="text-7">
 <p>
-Birchwood Abbey's weather hosts use the 1-Wire server (from the
-<code>owserver</code> package) and a 1-Wire USB adapter.  They use an
-unprivileged account (<code>monkey</code>) to run a SystemD service named
-<code>weatherd</code> (aka "the daemon").  The daemon is a Perl script that runs
-<code>owread</code> and logs the new measurements once per minute.
-</p>
-
-<p>
-The log files are collected by Monkey on Core (via <code>rsync</code>), then
-processed and published in campus web pages by The Weather Project's
-code (old, using <code>gnuplot(1)</code>, and so&#x2026; unpublished).
-</p>
-</div>
-<div id="outline-container-org6925511" class="outline-3">
-<h3 id="org6925511"><span class="section-number-3">7.1.</span> The Abbey Weather Hardware</h3>
-<div class="outline-text-3" id="text-7-1">
-<p>
-The abbey currently has one weather host, Gate, and a couple 1-Wire
-sensor modules.  The modules measure inside and outside temperature
-and humidity.  Their desired locations are 7-8m from the core servers
-so they are plugged into a custom Y cable, with the inside sensor
-cable spliced into the middle of the outside/main cable.  The proximal
-end's RJ11 plugs into a 1-Wire USB adapter (a DS9490R) plugged into
-Gate.  The outside end goes out the window with the Starlink cable.
-</p>
-</div>
-</div>
-<div id="outline-container-org0b52261" class="outline-3">
-<h3 id="org0b52261"><span class="section-number-3">7.2.</span> The Abbey Weather Host Setup</h3>
-<div class="outline-text-3" id="text-7-2">
-<p>
-The Ansible code in the <code>abbey-weather</code> role assumes it is working
-with a cloistered host (as described in <a href="#org110d7b3">Cloistering</a>) and proceeds in
-two phases.  The first installs the <code>ow-server</code> package and configures
-it to use a DS9490 (USB adapter) rather than a debugging fake.  After
-the first <code>./abbey config new</code>, the new weather host seems to need a
-reboot before the 1-Wire bus becomes visible via <code>owdir</code>.
+Birchwood Abbey now uses Home Assistant to record and display weather
+data from an Ecowitt GW2001 IoT gateway connecting wirelessly to a
+WS90 (7 function weather station) and a couple WN31s (temp/humidity
+sensors).
 </p>
 
 <p>
-After a reboot <code>owdir</code> should list one or more type 26 device IDs.
-Listing them (e.g. running <code>owdir /26.nnnnnnnn</code> or <code>owdir
-/26.nnnnnnnn/HIH</code>) should reveal "files" named <q>temperature</q> and
-<q>HIH/humidity</q>.  These pseudo-file paths are used in the daemon script
-below.  A test session is shown below.
+The configuration of the GW2001 IoT hub involved turning off the Wi-Fi
+access point, and disabling unused channels.  The hub reports the data
+from all sensors in range, <i>anyone's</i> sensors.  These new data sources
+are noticed and recorded by Home Assistant automatically as similarly
+equipped campers come and go.  Disabling unused channels helps avoid
+these distractions.
 </p>
 
-<pre class="example" id="orgd2ab943">
-monkey@new$ owdir
-...
-    /26.2153B6000000/
-...
-monkey@new$ owdir /26.2153B6000000
-...
-    /26.2153B6000000/temperature
-...
-monkey@new$ owread /26.2153B6000000/temperature; echo
-26.125
-monkey@new$ 
-</pre>
-
 <p>
-The second phase of weather host configuration waits for the host-
-specific weather daemon script to appear in the role's <q>files/</q>.
+The configuration of Home Assistant involved installing the Ecowitt
+"integration".  This was accomplished by choosing "Settings", then
+"Devices &amp; services", then "Add Integration", and searching for
+"Ecowitt".  Once installed, the integration created dozens of weather
+entities which were organized into an "Abbey" dashboard.
 </p>
 </div>
 </div>
-<div id="outline-container-org6ea9cdd" class="outline-3">
-<h3 id="org6ea9cdd"><span class="section-number-3">7.3.</span> The Abbey Weather Daemons</h3>
-<div class="outline-text-3" id="text-7-3">
+<div id="outline-container-org2c65dbc" class="outline-2">
+<h2 id="org2c65dbc"><span class="section-number-2">8.</span> The Abbey DVR Role</h2>
+<div class="outline-text-2" id="text-8">
 <p>
-Different weather hosts, with different 1-Wire devices, need different
-daemon scripts, to call <code>owread</code> with different paths (containing the
-IDs of each host's devices).  At the moment there is just the
-one weather host, <code>anoat</code>.
+The abbey uses AgentDVR to record video from PoE IP HD security
+cameras.  The "download" button on iSpy's Download page
+(<a href="https://www.ispyconnect.com/download">https://www.ispyconnect.com/download</a>), when "Agent DVR - Linux/
+macOS/ RPi" is chosen, suggests the following command lines (the
+second of which is broken across three lines).
 </p>
 
 <div class="org-src-container">
-<a href="roles_t/abbey-weather/files/daemon-anoat"><q>roles_t/abbey-weather/files/daemon-anoat</q></a><pre class="src src-perl"><span class="org-comment-delimiter">#</span><span class="org-comment">!/usr/bin/perl -w</span>
-<span class="org-comment-delimiter"># </span><span class="org-comment">-*- CPerl -*-</span>
-<span class="org-comment-delimiter">#</span>
-<span class="org-comment-delimiter"># </span><span class="org-comment">Weather/daemon</span>
-<span class="org-comment-delimiter">#</span>
-<span class="org-comment-delimiter"># </span><span class="org-comment">Fetches data from the local owserver once per minute.  Appends to</span>
-<span class="org-comment-delimiter"># </span><span class="org-comment">Log/{In,Out}side/YEAR/MONTH/DAY.txt.</span>
-
-<span class="org-keyword">use</span> <span class="org-constant">strict</span>;
-<span class="org-keyword">use</span> <span class="org-constant">IO::File</span>;
-<span class="org-keyword">use</span> <span class="org-constant">Date::Format</span>;
-
-<span class="org-keyword">my</span> $<span class="org-variable-name">ILOG</span>;
-<span class="org-keyword">my</span> $<span class="org-variable-name">OLOG</span>;
-<span class="org-keyword">my</span> $<span class="org-variable-name">ymd</span> = <span class="org-string">""</span>;
-<span class="org-keyword">sub</span> <span class="org-function-name">mymkdir</span> ($);
-<span class="org-keyword">sub</span> <span class="org-function-name">reopen_logs</span> ()
-{
-  <span class="org-keyword">my</span> $<span class="org-variable-name">time</span> = time;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">datime</span> = time2str (<span class="org-string">"%Y-%m-%d %H:%M:%S"</span>, $<span class="org-variable-name">time</span>, <span class="org-string">"UTC"</span>);
-  <span class="org-keyword">my</span> ($<span class="org-variable-name">year</span>, $<span class="org-variable-name">month</span>, $<span class="org-variable-name">day</span>) = $<span class="org-variable-name">datime</span> =~ <span class="org-string">/^(\d{4})-(\d\d)-(\d\d) /</span>;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">new_ymd</span> = <span class="org-string">"$year/$month/$day"</span>;
-  <span class="org-keyword">return</span> <span class="org-keyword">if</span> $<span class="org-variable-name">new_ymd</span> eq $<span class="org-variable-name">ymd</span>;
-  close $<span class="org-variable-name">ILOG</span> <span class="org-keyword">if</span> defined $<span class="org-variable-name">ILOG</span>;
-  close $<span class="org-variable-name">OLOG</span> <span class="org-keyword">if</span> defined $<span class="org-variable-name">OLOG</span>;
-  umask 07;
-  mymkdir <span class="org-string">"Inside/$year/$month"</span>;
-  mymkdir <span class="org-string">"Outside/$year/$month"</span>;
-  umask 027;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">filename</span> = <span class="org-string">"Inside/$new_ymd.txt"</span>;
-  $<span class="org-variable-name">ILOG</span> = new IO::File;
-  open $<span class="org-variable-name">ILOG</span>, <span class="org-string">"&gt;&gt;$filename"</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not open $filename: $!\n"</span>;
-  $<span class="org-variable-name">filename</span> = <span class="org-string">"Outside/$new_ymd.txt"</span>;
-  $<span class="org-variable-name">OLOG</span> = new IO::File;
-  open $<span class="org-variable-name">OLOG</span>, <span class="org-string">"&gt;&gt;$filename"</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not open $filename: $!\n"</span>;
-  $<span class="org-variable-name">ymd</span> = $<span class="org-variable-name">new_ymd</span>;
-}
-
-<span class="org-keyword">sub</span> <span class="org-function-name">logit</span> ($$$);
-<span class="org-keyword">sub</span> <span class="org-function-name">main</span> () {
-  <span class="org-keyword">die</span> <span class="org-string">"usage: $0\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 0;
-  $<span class="org-variable-name">0</span> = <span class="org-string">"weatherd"</span>;
-  chdir <span class="org-string">"/home/monkey/Weather/Log"</span> or <span class="org-keyword">die</span>;
-  umask 027;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">start</span> = time;
-  {
-    <span class="org-keyword">my</span> $<span class="org-variable-name">secs</span> = 60 - $<span class="org-variable-name">start</span> % 60;
-    $<span class="org-variable-name">start</span> += $<span class="org-variable-name">secs</span>;
-    sleep ($<span class="org-variable-name">secs</span>);
-  }
-  <span class="org-keyword">while</span> (1) {
-    reopen_logs;
-    logit $<span class="org-variable-name">OLOG</span>, <span class="org-string">"T"</span>, <span class="org-string">"/26.2153B6000000/temperature"</span>;
-    logit $<span class="org-variable-name">OLOG</span>, <span class="org-string">"H"</span>, <span class="org-string">"/26.2153B6000000/HIH4000/humidity"</span>;
-    logit $<span class="org-variable-name">ILOG</span>, <span class="org-string">"T"</span>, <span class="org-string">"/26.8859B6000000/temperature"</span>;
-    logit $<span class="org-variable-name">ILOG</span>, <span class="org-string">"H"</span>, <span class="org-string">"/26.8859B6000000/HIH4000/humidity"</span>;
-    $<span class="org-variable-name">start</span> += 60;
-    <span class="org-keyword">my</span> $<span class="org-variable-name">now</span> = time;
-    <span class="org-keyword">while</span> ($<span class="org-variable-name">start</span> &lt; $<span class="org-variable-name">now</span>) { $<span class="org-variable-name">start</span> += 60; }
-    <span class="org-keyword">my</span> $<span class="org-variable-name">secs</span> = $<span class="org-variable-name">start</span> - $<span class="org-variable-name">now</span>;
-    sleep  ($<span class="org-variable-name">secs</span>);
-  }
-}
-
-<span class="org-keyword">sub</span> <span class="org-function-name">logit</span> ($$$)
-{
-  <span class="org-keyword">my</span> ($<span class="org-variable-name">log</span>, $<span class="org-variable-name">name</span>, $<span class="org-variable-name">query</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
-
-  <span class="org-keyword">my</span> $<span class="org-variable-name">tries</span> = 0;
-  <span class="org-keyword">while</span> ($<span class="org-variable-name">tries</span> &lt; 3) {
-    <span class="org-keyword">my</span> $<span class="org-variable-name">time</span> = time;
-    <span class="org-keyword">my</span> $<span class="org-variable-name">datime</span> = time2str (<span class="org-string">"%Y-%m-%d %H:%M:%S"</span>, $<span class="org-variable-name">time</span>, <span class="org-string">"UTC"</span>);
-    $<span class="org-variable-name">tries</span> += 1;
-    <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">lines</span> = <span class="org-string">`/usr/bin/owread $query`</span>;
-    chomp @<span class="org-perl-non-scalar-variable">lines</span>;
-    <span class="org-keyword">my</span> $<span class="org-variable-name">status</span> = $?;
-    <span class="org-keyword">my</span> $<span class="org-variable-name">sig</span> = $<span class="org-variable-name">status</span> &amp; 127;
-    $<span class="org-variable-name">status</span> &gt;&gt;= 8;
-    <span class="org-keyword">if</span> ($<span class="org-variable-name">status</span> != 0) {
-      <span class="org-keyword">my</span> $<span class="org-variable-name">L</span> = join <span class="org-string">"\\n"</span>, @<span class="org-perl-non-scalar-variable">lines</span>;
-      print $<span class="org-variable-name">log</span> <span class="org-string">"$datime\t$name\terror: status $status: $L\n"</span>;
-      $<span class="org-variable-name">log</span>-&gt;flush;
-    } <span class="org-keyword">elsif</span> (@<span class="org-perl-non-scalar-variable">lines</span> != 1) {
-      <span class="org-keyword">my</span> $<span class="org-variable-name">L</span> = join <span class="org-string">"\\n"</span>, @<span class="org-perl-non-scalar-variable">lines</span>;
-      print $<span class="org-variable-name">log</span> <span class="org-string">"$datime\t$name\terror: multiple lines: $L\n"</span>;
-      $<span class="org-variable-name">log</span>-&gt;flush;
-    } <span class="org-keyword">elsif</span> ($<span class="org-variable-name">lines</span>[0] !~ <span class="org-string">/^ *(-?\d+(\.\d+)?)$/</span>) {
-      <span class="org-keyword">my</span> $<span class="org-variable-name">L</span> = $<span class="org-variable-name">lines</span>[0];
-      print $<span class="org-variable-name">log</span> <span class="org-string">"$datime\t$name\terror: bogus line: $L\n"</span>;
-      $<span class="org-variable-name">log</span>-&gt;flush;
-    } <span class="org-keyword">else</span> {
-      <span class="org-keyword">my</span> $<span class="org-variable-name">datum</span> = $<span class="org-variable-name">1</span>;
-      print $<span class="org-variable-name">log</span> <span class="org-string">"$datime\t$name\t$datum\n"</span>;
-      $<span class="org-variable-name">log</span>-&gt;flush;
-      <span class="org-keyword">return</span>;
-    }
-  }
-}
-
-<span class="org-keyword">sub</span> <span class="org-function-name">mymkdir</span> ($)
-{
-  <span class="org-keyword">my</span> ($<span class="org-variable-name">dirpath</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
-
-  <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">path_names</span> = split <span class="org-string">/\//</span>, $<span class="org-variable-name">dirpath</span>;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">path</span>;
-  <span class="org-keyword">if</span> (!$<span class="org-variable-name">path_names</span>[0]) {
-    $<span class="org-variable-name">path</span> = <span class="org-string">"/"</span>;
-    shift @<span class="org-perl-non-scalar-variable">path_names</span>;
-  } <span class="org-keyword">else</span> {
-    $<span class="org-variable-name">path</span> = <span class="org-string">"."</span>;
-  }
-  <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">created</span>;
-  <span class="org-keyword">while</span> (@<span class="org-perl-non-scalar-variable">path_names</span>) {
-    $<span class="org-variable-name">path</span> .= <span class="org-string">"/"</span> . shift @<span class="org-perl-non-scalar-variable">path_names</span>;
-    <span class="org-keyword">if</span> (! -d $<span class="org-variable-name">path</span>) {
-      <span class="org-keyword">if</span> (-e $<span class="org-variable-name">path</span>) {
-        <span class="org-keyword">die</span> <span class="org-string">"mkdir $dirpath: already exists; not a directory!\n"</span>;
-      }
-      <span class="org-keyword">if</span> (! mkdir $<span class="org-variable-name">path</span>) {
-        <span class="org-keyword">die</span> <span class="org-string">"mkdir $path: $!\n"</span>;
-      } <span class="org-keyword">else</span> {
-        chmod 02775, $<span class="org-variable-name">path</span>;
-        push @<span class="org-perl-non-scalar-variable">created</span>, $<span class="org-variable-name">path</span>;
-      }
-    }
-  }
-  <span class="org-keyword">return</span> @<span class="org-perl-non-scalar-variable">created</span>;
-}
-
-main;
+<pre class="src src-sh">sudo apt-get install curl
+bash &lt;(curl -s <span class="org-string">"https://raw.githubusercontent.com/\</span>
+<span class="org-string">ispysoftware/agent-install-scripts/main/v2/\</span>
+<span class="org-string">install.sh"</span>)
 </pre>
 </div>
 
 <p>
-The above Perl script uses the <code>Date::Format</code> module, which is
-installed by the following task.
+Ansible assists by creating the system user <code>agentdvr</code> and granting it
+enough <code>sudo</code> latitude to run the installer as instructed above.
+Though a system user, the account gets a home directory,
+<q>/home/agentdvr/</q> in which to do the installation.  The rest of the
+DVR role, "phase two", waits until AgentDVR is installed.
 </p>
 
-<div class="org-src-container">
-<a href="roles_t/abbey-weather/tasks/main.yml"><q>roles_t/abbey-weather/tasks/main.yml</q></a><pre class="src src-conf">---
-- name: Install weather daemon packages.
-  become: yes
-  <span class="org-variable-name">apt: pkg</span>=libtimedate-perl
-</pre>
-</div>
-</div>
-</div>
-<div id="outline-container-orgfe87cd1" class="outline-3">
-<h3 id="orgfe87cd1"><span class="section-number-3">7.4.</span> Install 1-Wire Server</h3>
-<div class="outline-text-3" id="text-7-4">
 <p>
-This next task installs the 1-Wire server and shell commands.  The
-abbey uses the Dallas Semiconductor DS9490R, a USB to 1-Wire adapter,
-on all its weather hosts, so it also configures the server to use the
-USB adapter (rather than a test "fake" adapter).
+AgentDVR is installed, after Ansible has set things up, by running the
+command lines prescribed by iSpy while logged in as <code>agentdvr</code> with
+the current default directory <q>/home/agentdvr/</q>.  The installer should
+create the <q>/home/agentdvr/AgentDVR/</q> directory.  Its offer to install
+a system service is declined.
 </p>
 
-<div class="org-src-container">
-<a href="roles_t/abbey-weather/tasks/main.yml"><q>roles_t/abbey-weather/tasks/main.yml</q></a><pre class="src src-conf">
-- name: Install 1-Wire server.
-  become: yes
-  apt:
-    pkg: [ owserver, ow-shell ]
-
-- name: Configure 1-Wire server.
-  become: yes
-  lineinfile:
-    path: /etc/owfs.conf
-    regexp: <span class="org-string">"{{ item.regexp }}"</span>
-    line: <span class="org-string">"{{ item.line }}"</span>
-    backrefs: yes
-  loop:
-  - { regexp: <span class="org-string">'^[# ]*server: *FAKE(.*)$'</span>, line: <span class="org-string">'#server: FAKE\1'</span> }
-  - { regexp: <span class="org-string">'^[# ]*server: *usb(.*)$'</span>, line: <span class="org-string">'server: usb\1'</span> }
-</pre>
-</div>
-</div>
-</div>
-<div id="outline-container-org816673b" class="outline-3">
-<h3 id="org816673b"><span class="section-number-3">7.5.</span> Install Rsync</h3>
-<div class="outline-text-3" id="text-7-5">
 <p>
-Monkey on Core will want to download log records (files) using
-<code>rsync(1)</code>.
+After AgentDVR is installed, when the <q>/home/agentdvr/AgentDVR/</q>
+directory exists, Ansible is run again to install the system service.
 </p>
-
-<div class="org-src-container">
-<a href="roles_t/abbey-weather/tasks/main.yml"><q>roles_t/abbey-weather/tasks/main.yml</q></a><pre class="src src-conf">
-- name: Install Rsync.
-  become: yes
-  <span class="org-variable-name">apt: pkg</span>=rsync
-</pre>
 </div>
-</div>
-</div>
-<div id="outline-container-org13dabcd" class="outline-3">
-<h3 id="org13dabcd"><span class="section-number-3">7.6.</span> Create Monkey</h3>
-<div class="outline-text-3" id="text-7-6">
+<div id="outline-container-orgb219961" class="outline-3">
+<h3 id="orgb219961"><span class="section-number-3">8.1.</span> Create User <code>agentdvr</code></h3>
+<div class="outline-text-3" id="text-8-1">
 <p>
-The weather daemon is run by an unprivileged <code>monkey</code> account (<i>not</i>
-<code>sysadm</code>) which allows <code>monkey</code> on Core shell access.  This is also
-executed during the initial phase of configuration, allowing the
-administrator to login on the new weather host as <code>monkey</code> and thus to
-test access to the 1-Wire adapter and devices.  To facilitate
-debugging, the <code>sysadm</code> account is included in the <code>monkey</code> group.
+AgentDVR runs as the system user <code>agentdvr</code>, which is created here.
 </p>
 
 <div class="org-src-container">
-<a href="roles_t/abbey-weather/tasks/main.yml"><q>roles_t/abbey-weather/tasks/main.yml</q></a><pre class="src src-conf">
-- name: Create monkey.
+<a href="roles_t/abbey-dvr/tasks/main.yml"><q>roles_t/abbey-dvr/tasks/main.yml</q></a><pre class="src src-conf">---
+- name: Create agentdvr.
   become: yes
   user:
-    name: monkey
+    name: agentdvr
     system: yes
+    home: /home/agentdvr
+    shell: /bin/bash
+    append: yes
+    groups: video
 
-- name: Authorize monkey@core.
-  become: yes
-  vars:
-    pubkeyfile: ../Secret/ssh_monkey/id_rsa.pub
-  authorized_key:
-    user: monkey
-    key: <span class="org-string">"{{ lookup('file', pubkeyfile) }}"</span>
-    manage_dir: yes
-
-- name: Add {{ ansible_user }} to monkey group.
+- name: Add {{ ansible_user }} to agentdvr group.
   become: yes
   user:
     name: <span class="org-string">"{{ ansible_user }}"</span>
     append: yes
-    groups: monkey
-</pre>
-</div>
-</div>
-</div>
-<div id="outline-container-org225880e" class="outline-3">
-<h3 id="org225880e"><span class="section-number-3">7.7.</span> Install Weather Daemon</h3>
-<div class="outline-text-3" id="text-7-7">
-<p>
-The weather daemon is kept alive as a Systemd service unit.  This task
-creates and starts that service <i>after</i> the host-specific
-<q>files/daemon-HOST</q> file becomes available.
-</p>
-
-<p>
-The <code>ExecStartPre=/bin/sleep 30</code> is intended to avoid recent hangs in
-<code>owread</code>.
-</p>
+    groups: agentdvr
 
-<div class="org-src-container">
-<a href="roles_t/abbey-weather/tasks/main.yml"><q>roles_t/abbey-weather/tasks/main.yml</q></a><pre class="src src-conf">
-- name: Install weather directory.
+- name: Create /home/agentdvr/.
   become: yes
   file:
-    path: /home/monkey/Weather/Log
+    path: /home/agentdvr
     state: directory
-    owner: monkey
-    group: monkey
-    <span class="org-variable-name">mode: u</span>=rwx,g=rx,o=rx
-
-- name: Test for weather daemon script.
-  vars:
-    dir: ../roles/abbey-weather/files
-    file: <span class="org-string">"{{ dir }}/daemon-{{ inventory_hostname }}"</span>
-  <span class="org-variable-name">stat: path</span>=<span class="org-string">"{{ file }}"</span>
-  delegate_to: localhost
-  register: weather
-
-- name: Note missing weather daemon script.
-  vars:
-    dir: ../roles/abbey-weather/files
-    script: <span class="org-string">"{{ dir }}/daemon-{{ inventory_hostname }}"</span>
-  debug:
-    msg: <span class="org-string">"{{ script }}: not found"</span>
-  when: not weather.stat.exists
-
-- name: Install weather daemon.
-  become: yes
-  vars:
-    dir: ../roles/abbey-weather/files
-    script: <span class="org-string">"{{ dir }}/daemon-{{ inventory_hostname }}"</span>
-  copy:
-    src: <span class="org-string">"{{ script }}"</span>
-    dest: /home/monkey/Weather/daemon
-    owner: monkey
-    group: monkey
-    <span class="org-variable-name">mode: u</span>=rwx,g=rx,o=
-  when: weather.stat.exists
-
-- name: Install weatherd service.
-  become: yes
-  copy:
-    content: |
-      [<span class="org-type">Unit</span>]
-      <span class="org-variable-name">Description</span>=Weather Logger
-      <span class="org-variable-name">After</span>=owserver.service
-
-      [<span class="org-type">Service</span>]
-      <span class="org-variable-name">User</span>=monkey
-      <span class="org-variable-name">ExecStartPre</span>=/bin/sleep 30
-      <span class="org-variable-name">ExecStart</span>=/home/monkey/Weather/daemon
-      <span class="org-variable-name">Restart</span>=always
-
-      [<span class="org-type">Install</span>]
-      <span class="org-variable-name">WantedBy</span>=multi-user.target
-    dest: /etc/systemd/system/weatherd.service
-  when: weather.stat.exists
-  notify:
-  - Reload Systemd.
-  - Restart weather daemon.
-
-- name: Enable/Start weather daemon.
-  become: yes
-  systemd:
-    service: weatherd
-    enabled: yes
-    state: started
-  when: weather.stat.exists
+    owner: agentdvr
+    group: agentdvr
+    <span class="org-variable-name">mode: u</span>=rwx,g=rwxs,o=rx
 </pre>
 </div>
-
-<div class="org-src-container">
-<a href="roles_t/abbey-weather/handlers/main.yml"><q>roles_t/abbey-weather/handlers/main.yml</q></a><pre class="src src-conf">---
-- name: Reload Systemd.
-  become: yes
-  command: systemctl daemon-reload
-
-- name: Restart weather daemon.
-  become: yes
-  systemd:
-    service: weatherd
-    state: restarted
-</pre>
-</div>
-</div>
 </div>
 </div>
-<div id="outline-container-org2c65dbc" class="outline-2">
-<h2 id="org2c65dbc"><span class="section-number-2">8.</span> The Abbey DVR Role</h2>
-<div class="outline-text-2" id="text-8">
-<p>
-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 <q>/Zoneminder/</q>, the mount point for a separate, large
-storage volume.  It follows the instructions in Zoneminder's
-<q>README.Debian</q> (in <q>/usr/share/doc/zoneminder/</q>) to create the <code>zm</code>
-database and configure Apache.
-</p>
-</div>
-<div id="outline-container-org4fe0a29" class="outline-3">
-<h3 id="org4fe0a29"><span class="section-number-3">8.1.</span> DVR Machine Setup</h3>
-<div class="outline-text-3" id="text-8-1">
+<div id="outline-container-org9c7e794" class="outline-3">
+<h3 id="org9c7e794"><span class="section-number-3">8.2.</span> Authorize User <code>agentdvr</code></h3>
+<div class="outline-text-3" id="text-8-2">
 <p>
-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 <code>dvrs</code> group in
-<a href="#orgd0676df">10.2</a>, run Ansible to get the Zoneminder software installed.
+The AgentDVR installer is also run by <code>agentdvr</code>, which is authorized
+to run a handful of system commands.  This small set is sufficient
+<i>if</i> the offer to create the system service is declined.  In that
+case, the installer will run the program in the terminal.
 </p>
 
-<pre class="example">
-./abbey config HOST
+<div class="org-src-container">
+<a href="roles_t/abbey-dvr/tasks/main.yml"><q>roles_t/abbey-dvr/tasks/main.yml</q></a><pre class="src src-conf">
+- name: Authorize agentdvr.
+  copy:
+    content: |
+      <span class="org-variable-name">ALL ALL</span>=(agentdvr) NOPASSWD: /bin/systemctl,/bin/apt-get,\
+          /sbin/adduser,/sbin/usermod
+    dest: /etc/sudoers.d/agentdvr
 </pre>
-
-
-<p>
-Several configuration steps will be skipped because <q>/Zoneminder/</q> has
-not been created yet.  To proceed, first create the database and
-database user manually, as described in section <a href="#org6c0f481">Manually Create
-Zoneminder DB and User</a>.
-</p>
 </div>
 </div>
-<div id="outline-container-org65ae5a4" class="outline-3">
-<h3 id="org65ae5a4"><span class="section-number-3">8.2.</span> Create <q>/Zoneminder/</q></h3>
-<div class="outline-text-3" id="text-8-2">
-<p>
-<q>/Zoneminder/</q> should be a separate, large volume lest Zoneminder fill
-the root file system.  For acceptable performance, <q>/Zoneminder/</q>
-should also be the mount point of a solid-state disk (SSD).  A
-symbolic link at <q>/var/cache/zoneminder/events</q> targets <q>/Zoneminder</q>
-to make it Zoneminder's "default" storage area.  (The <code>PurgeWhenFull</code>
-filter only works with the default storage area in v1.34.)
-</p>
 </div>
-</div>
-<div id="outline-container-orgd30766b" class="outline-3">
-<h3 id="orgd30766b"><span class="section-number-3">8.3.</span> Continue Zoneminder Configuration</h3>
+<div id="outline-container-org0f20387" class="outline-3">
+<h3 id="org0f20387"><span class="section-number-3">8.3.</span> Test For <q>AgentDVR/</q></h3>
 <div class="outline-text-3" id="text-8-3">
 <p>
-Once the <code>zm</code> database (and <code>zmuser</code> database user) are created, and a
-large volume mounted at <q>/Zoneminder/</q>, Ansible can continue with the
-Zoneminder configuration.
-</p>
-
-<pre class="example">
-./abbey configure HOST
-</pre>
-
-
-<p>
-Configuring Zoneminder's cameras is still a manual process as
-described in the final section, <a href="#org1115114">Configure Cameras</a>, below.
-</p>
-</div>
-</div>
-<div id="outline-container-org114a404" class="outline-3">
-<h3 id="org114a404"><span class="section-number-3">8.4.</span> Include Abbey Variables</h3>
-<div class="outline-text-3" id="text-8-4">
-<p>
-Private variables in <q>private/vars-abbey.yml</q> are needed, and included
-here, as in the <code>abbey-core</code> role.  The file path is relative to the
-playbook's directory, <q>playbooks/</q>.
+The following task probes for the <q>/home/agentdvr/AgentDVR/</q>
+directory, to detect that the build/install process has completed.  It
+registers the results in the <code>agentdvr</code> variable.  Several of the
+remaining installation steps are skipped unless
+<code>agentdvr.stat.exists</code>.
 </p>
 
 <div class="org-src-container">
-<a href="roles_t/abbey-dvr/tasks/main.yml"><q>roles_t/abbey-dvr/tasks/main.yml</q></a><pre class="src src-conf">---
-- name: Include private abbey variables.
-  include_vars: ../private/vars-abbey.yml
+<a href="roles_t/abbey-dvr/tasks/main.yml"><q>roles_t/abbey-dvr/tasks/main.yml</q></a><pre class="src src-conf">
+- name: Test for AgentDVR directory.
+  stat:
+    path: /home/agentdvr/AgentDVR
+  register: agentdvr
+- debug:
+    msg: <span class="org-string">"/home/agentdvr/AgentDVR/ does not yet exist"</span>
+  when: not agentdvr.stat.exists
 </pre>
 </div>
 </div>
 </div>
-<div id="outline-container-orgccc0d2c" class="outline-3">
-<h3 id="orgccc0d2c"><span class="section-number-3">8.5.</span> Install Zoneminder v1.34</h3>
-<div class="outline-text-3" id="text-8-5">
-<p>
-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
-<q>/Zoneminder/</q> 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 <q>/Zoneminder/</q> got
-Zoneminder 1.34 to work reliably.
-</p>
-
+<div id="outline-container-org6053bf2" class="outline-3">
+<h3 id="org6053bf2"><span class="section-number-3">8.4.</span> Create AgentDVR Service</h3>
+<div class="outline-text-3" id="text-8-4">
 <p>
-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 <q>/usr/share/doc/zoneminder/README.Debian.gz</q>.
+This service definition came from the template downloaded (from <a href="https://raw.githubusercontent.com/ispysoftware/agent-install-scripts/main/v2/AgentDVR.service">here</a>)
+by the installer, specifically the <q>linux_setup2.sh</q> script downloaded
+by <q>install.sh</q>.
 </p>
 
 <div class="org-src-container">
 <a href="roles_t/abbey-dvr/tasks/main.yml"><q>roles_t/abbey-dvr/tasks/main.yml</q></a><pre class="src src-conf">
-- name: Install Zoneminder.
+- name: Install AgentDVR.service.
   become: yes
-  <span class="org-variable-name">apt: pkg</span>=zoneminder
+  copy:
+    content: |
+      [<span class="org-type">Unit</span>]
+      <span class="org-variable-name">Description</span>=AgentDVR
 
-- name: Enable Apache modules for Zoneminder.
-  become: yes
-  apache2_module:
-    name: <span class="org-string">"{{ item }}"</span>
-  loop: [ cgid, rewrite, expires, headers ]
-  notify: Restart Apache2.
+      [<span class="org-type">Service</span>]
+      <span class="org-variable-name">WorkingDirectory</span>=/home/agentdvr/AgentDVR
+      <span class="org-variable-name">ExecStart</span>=/home/agentdvr/AgentDVR/Agent
 
-- name: Enable Zoneminder Apache configuration.
-  become: yes
-  command:
-    cmd: a2enconf zoneminder
-    creates: /etc/apache2/conf-enabled/zoneminder.conf
-  notify: Restart Apache2.
+      <span class="org-comment-delimiter"># </span><span class="org-comment">fix memory management issue with dotnet core</span>
+      <span class="org-variable-name">Environment</span>=<span class="org-string">"MALLOC_TRIM_THRESHOLD_=100000"</span>
 
-- name: Configure MySQL for Zoneminder.
-  become: yes
-  copy:
-    content: |
-      [<span class="org-type">mysqld</span>]
-      <span class="org-variable-name">sql_mode</span> = NO_ENGINE_SUBSTITUTION
-    dest: /etc/mysql/conf.d/zoneminder.cnf
-  notify: Restart MySQL.
+      <span class="org-comment-delimiter"># </span><span class="org-comment">to query logs using journalctl, set a logical name here</span>
+      <span class="org-variable-name">SyslogIdentifier</span>=AgentDVR
 
-- name: Configure PHP date.timezone.
-  become: yes
-  lineinfile:
-    <span class="org-variable-name">regexp: date.timezone ?</span>=
-    <span class="org-variable-name">line: date.timezone</span> = {{ lookup(<span class="org-string">'file'</span>, <span class="org-string">'/etc/timezone'</span>) }}
-    path: <span class="org-string">"{{ item }}"</span>
-  loop:
-  - /etc/php/8.2/cli/php.ini
-  - /etc/php/8.2/apache2/php.ini
-  notify: Restart Apache2.
+      <span class="org-variable-name">User</span>=agentdvr
+
+      <span class="org-comment-delimiter"># </span><span class="org-comment">ensure the service automatically restarts</span>
+      <span class="org-variable-name">Restart</span>=always
+      <span class="org-comment-delimiter"># </span><span class="org-comment">amount of time to wait before restarting the service</span>
+      <span class="org-variable-name">RestartSec</span>=5
 
-- name: Enable/Start Apache2.
+      [<span class="org-type">Install</span>]
+      <span class="org-variable-name">WantedBy</span>=multi-user.target
+    dest: /etc/systemd/system/AgentDVR.service
+  when: agentdvr.stat.exists
+
+- name: Enable/Start AgentDVR.service.
   become: yes
   systemd:
-    service: apache2
+    service: AgentDVR
     enabled: yes
     state: started
+  when: agentdvr.stat.exists
 </pre>
 </div>
-
-<div class="org-src-container">
-<a href="roles_t/abbey-dvr/handlers/main.yml"><q>roles_t/abbey-dvr/handlers/main.yml</q></a><pre class="src src-conf">---
-- name: Restart MySQL.
-  become: yes
-  systemd:
-    service: mysql
-    state: restarted
-
-- name: Restart Apache2.
-  become: yes
-  systemd:
-    service: apache2
-    state: restarted
-</pre>
 </div>
-
+</div>
+<div id="outline-container-orgbdf0cde" class="outline-3">
+<h3 id="orgbdf0cde"><span class="section-number-3">8.5.</span> Create AgentDVR Storage</h3>
+<div class="outline-text-3" id="text-8-5">
 <p>
-The following Rsyslog configuration drop-in gets Zoneminder's natter
-out of <q>/var/log/syslog</q>.
+The abbey uses a separate volume to store surveillance recordings,
+lest the DVR program fill the root file system.  The volume is mounted
+at <q>/DVR/</q>.  The following tasks create <q>/DVR/AgentDVR/video/</q>
+(whether a large volume is mounted there <i>or not</i>!) with appropriate
+permissions so that the instructions for configuring a default storage
+location do not fail.
 </p>
 
 <div class="org-src-container">
 <a href="roles_t/abbey-dvr/tasks/main.yml"><q>roles_t/abbey-dvr/tasks/main.yml</q></a><pre class="src src-conf">
-- name: Use /var/log/zoneminder.log
+- name: Create /DVR/AgentDVR/.
   become: yes
-  copy:
-    content: |
-      :programname,startswith,<span class="org-string">"zm"</span> -/var/log/zoneminder.log
-      &amp; stop
-    dest: /etc/rsyslog.d/40-zoneminder.conf
-</pre>
-</div>
-</div>
-</div>
-<div id="outline-container-org6940ae7" class="outline-3">
-<h3 id="org6940ae7"><span class="section-number-3">8.6.</span> Create Zoneminder Database</h3>
-<div class="outline-text-3" id="text-8-6">
-<p>
-Zoneminder's MariaDB database is created by the following task, when
-the <code>mysql_db</code> Ansible module supports <code>check_implicit_admin</code>.
-</p>
+  file:
+    state: directory
+    path: /DVR/AgentDVR
+    owner: agentdvr
+    group: agentdvr
+    <span class="org-variable-name">mode: u</span>=rwx,g=rxs,o=
 
-<div class="org-src-container">
-<pre class="src src-conf">
-- name: Create Zoneminder DB.
+- name: Create /DVR/AgentDVR/video/.
   become: yes
-  mysql_db:
-    check_implicit_admin: yes
-    name: zm
-    collation: utf8mb4_general_ci
-    encoding: utf8mb4
+  file:
+    state: directory
+    path: /DVR/AgentDVR/video
+    owner: agentdvr
+    group: agentdvr
+    <span class="org-variable-name">mode: u</span>=rwx,g=rxs,o=
 </pre>
 </div>
-
-<p>
-Unfortunately it does not currently, yet the institute prefers the
-more secure Unix socket authentication method.  Rather than create a
-privileged DB user, the <code>zm</code> database is created manually (below).
-</p>
 </div>
 </div>
-<div id="outline-container-org7213248" class="outline-3">
-<h3 id="org7213248"><span class="section-number-3">8.7.</span> Create Zoneminder DB User</h3>
-<div class="outline-text-3" id="text-8-7">
+<div id="outline-container-orgaaba8bb" class="outline-3">
+<h3 id="orgaaba8bb"><span class="section-number-3">8.6.</span> Configure IP Cameras</h3>
+<div class="outline-text-3" id="text-8-6">
 <p>
-The following task would create the DB user (<code>mysql_user</code> supports
-<code>check_implicit_admin</code>) <i>but</i> the <code>zm</code> database was not created above.
+A new security camera is setup as described in <a href="#org110d7b3">Cloistering</a>, after
+which the camera should be accessible by name on the abbey networks.
+Assuming <code>ping -c1 new</code> works, the camera's web interface will be
+accessible at <code>http://new/</code>.
 </p>
 
 <p>
-The DB user's password is taken from the <code>zoneminder_dbpass</code>
-variable, kept in <q>private/vars-abbey.yml</q>, and generated e.g. with
-the <code>apg -n 1 -x 12 -m 12</code> command.
+The administrator uses this to make the following changes.
 </p>
 
-<div class="org-src-container">
-<a href="private_ex/vars-abbey.yml"><q>private_ex/vars-abbey.yml</q></a><pre class="src src-conf">zoneminder_dbpass:           gakJopbikJadsEdd
-</pre>
-</div>
-
-<div class="org-src-container">
-<pre class="src src-conf">
-- name: Create Zoneminder DB user.
-  become: yes
-  mysql_user:
-    check_implicit_admin: yes
-    name: zmuser
-    password: <span class="org-string">"{{ zoneminder_dbpass }}"</span>
-    priv: &gt;-
-      zm.*:
-      lock tables,alter,create,index,select,insert,update,delete
-</pre>
-</div>
+<ul class="org-ul">
+<li>Set a password on the administrative account.</li>
+<li>Create an unprivileged user with a short password,
+e.g. <code>user:blah</code>.</li>
+<li>Set the frame rate to 5fps.  The abbey prefers HD resolution and
+long duration logs, thus fewer frames per second.</li>
+</ul>
 </div>
 </div>
-<div id="outline-container-org6c0f481" class="outline-3">
-<h3 id="org6c0f481"><span class="section-number-3">8.8.</span> Manually Create Zoneminder DB and User</h3>
-<div class="outline-text-3" id="text-8-8">
+<div id="outline-container-org45c6d54" class="outline-3">
+<h3 id="org45c6d54"><span class="section-number-3">8.7.</span> Configure AgentDVR's Cameras</h3>
+<div class="outline-text-3" id="text-8-7">
 <p>
-The Zoneminder database and database user are created manually with
-the following SQL (with the <code>zoneminder_dbpass</code> spliced in).  The SQL
-commands are entered at the SQL prompt of the <code>sudo mysql</code> command, or
-perhaps piped into the command.
+After Ansible has configured and started the AgentDVR service, its web
+UI will be available at <code>http://core:8090/</code>.  The initial Live View
+will be empty, overlayed with instructions to click the edit button.
 </p>
 
-<div class="org-src-container">
-<pre class="src src-sql"><span class="org-keyword">create</span> database zm
-    <span class="org-type">character</span> <span class="org-keyword">set</span> utf8mb4
-    <span class="org-keyword">collate</span> utf8mb4_general_ci;
-<span class="org-keyword">grant</span> lock tables,<span class="org-keyword">alter</span>,<span class="org-keyword">create</span>,index,<span class="org-keyword">select</span>,<span class="org-keyword">insert</span>,<span class="org-keyword">update</span>,<span class="org-keyword">delete</span>
-    <span class="org-keyword">on</span> zm.*
-    <span class="org-keyword">to</span> <span class="org-string">'zmuser'</span>@<span class="org-string">'localhost'</span>
-    identified <span class="org-keyword">by</span> <span class="org-string">'{{ zoneminder_dbpass }}'</span>;
-flush <span class="org-keyword">privileges</span>;
-exit;
-</pre>
-</div>
 
 <p>
-Finally, <code>zm</code>'s tables are created, completing the database setup,
+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.
 </p>
 
-<div class="org-src-container">
-<pre class="src src-sh">sudo mysql &lt; /usr/share/zoneminder/db/zm_create.sql
-</pre>
-</div>
-</div>
-</div>
-<div id="outline-container-orge311581" class="outline-3">
-<h3 id="orge311581"><span class="section-number-3">8.9.</span> Use <q>/Zoneminder/</q></h3>
-<div class="outline-text-3" id="text-8-9">
-<p>
-The following tasks start with a test for the existence of
-<q>/Zoneminder</q>.  Configuration tasks that require <q>/Zoneminder/</q> or the
-<code>zm</code> database are executed only when <code>zoneminder.stat.exists</code>.  The
-last "Link&#x2026;" task below "forces" the link, whether the target exists
-or not (yet).
-</p>
-
-<div class="org-src-container">
-<a href="roles_t/abbey-dvr/tasks/main.yml"><q>roles_t/abbey-dvr/tasks/main.yml</q></a><pre class="src src-conf">
-- name: Test for /Zoneminder/.
-  stat:
-    path: /Zoneminder
-  register: zoneminder
-- debug:
-    msg: <span class="org-string">"/Zoneminder/ does not yet exist"</span>
-  when: not zoneminder.stat.exists
-
-- name: Check /Zoneminder/.
-  become: yes
-  file:
-    state: directory
-    path: /Zoneminder
-    owner: www-data
-    group: www-data
-    <span class="org-variable-name">mode: u</span>=rwx,g=rx,o=rx
-  when: zoneminder.stat.exists
+<ul class="org-ul">
+<li>General:
+<ul class="org-ul">
+<li>On: yes</li>
+<li>Name: Outside</li>
+<li>Source Type: Network Camera
+<ul class="org-ul">
+<li>Username: user</li>
+<li>Password: blah</li>
+<li>Live URL: rtsp://new.birchwood.private:554/12</li>
+<li>Record URL: rtsp://new.birchwood.private:554/11</li>
+</ul></li>
+</ul></li>
+</ul>
 
-- name: Link to /Zoneminder/.
-  become: yes
-  file:
-    state: link
-    src: /Zoneminder
-    path: /var/cache/zoneminder/events
-    force: yes
-    follow: no
-</pre>
-</div>
-</div>
-</div>
-<div id="outline-container-orgd750b3c" class="outline-3">
-<h3 id="orgd750b3c"><span class="section-number-3">8.10.</span> Configure Zoneminder</h3>
-<div class="outline-text-3" id="text-8-10">
 <p>
-The remaining tasks ensure that the <q>/etc/zm/zm.conf</q> file has the
-proper permissions and contains the correct password.
+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.
 </p>
-
-<div class="org-src-container">
-<a href="roles_t/abbey-dvr/tasks/main.yml"><q>roles_t/abbey-dvr/tasks/main.yml</q></a><pre class="src src-conf">
-- name: Set /etc/zm/zm.conf permissions.
-  become: yes
-  file:
-    path: /etc/zm/zm.conf
-    owner: root
-    group: www-data
-    <span class="org-variable-name">mode: u</span>=rw,g=r,o=
-
-- name: Set Zoneminder passphrase.
-  become: yes
-  lineinfile:
-    regexp: <span class="org-string">'^ *ZM_DB_PASS *='</span>
-    <span class="org-variable-name">line: ZM_DB_PASS</span>={{ zoneminder_dbpass }}
-    path: /etc/zm/zm.conf
-</pre>
 </div>
-
+</div>
+<div id="outline-container-orgba96682" class="outline-3">
+<h3 id="orgba96682"><span class="section-number-3">8.8.</span> Configure AgentDVR's Default Storage</h3>
+<div class="outline-text-3" id="text-8-8">
 <p>
-Finally, Zoneminder's service unit can be enabled (and started) <i>if</i>
-<q>/Zoneminder/</q> exists.  It is assumed that, if <q>/Zoneminder/</q> exists,
-the <code>zm</code> database has also been created, and the service is ready to
-run.
+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 <q>/DVR/AgentDVR/</q>
+and the "default" toggle is set.  Several OK buttons then need to be
+pressed before the task is complete.
 </p>
-
-<div class="org-src-container">
-<a href="roles_t/abbey-dvr/tasks/main.yml"><q>roles_t/abbey-dvr/tasks/main.yml</q></a><pre class="src src-conf">
-- name: Enable/Start Zoneminder.
-  become: yes
-  systemd:
-    service: zoneminder
-    enabled: yes
-    state: started
-  when: zoneminder.stat.exists
-</pre>
 </div>
 </div>
-</div>
-<div id="outline-container-org1115114" class="outline-3">
-<h3 id="org1115114"><span class="section-number-3">8.11.</span> Configure Cameras</h3>
-<div class="outline-text-3" id="text-8-11">
-<p>
-A new security camera is setup as described in <a href="#org110d7b3">Cloistering</a>, after
-which the camera should be accessible by name on the abbey networks.
-Assuming <code>ping -c1 new</code> works, the camera's web interface will be
-accessible at <code>http://new/</code>.
-</p>
-
-<p>
-The abbey's administrator logs into <code>http://new/</code> 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. <code>user:gobbledygook</code>.
-</p>
-
+<div id="outline-container-orgcaf1cbe" class="outline-3">
+<h3 id="orgcaf1cbe"><span class="section-number-3">8.9.</span> Configure AgentDVR's Recordings</h3>
+<div class="outline-text-3" id="text-8-9">
 <p>
-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 <code>http://security/zm/</code>.)  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.)
+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).
 </p>
 
 <ul class="org-ul">
-<li>In the "General" tab, specify:
+<li>Recording:
 <ul class="org-ul">
-<li>Name: Front</li>
-<li>(Server: None)</li>
-<li>(Source type: Ffmpeg)</li>
-<li>Function: Record</li>
-<li>Enabled: yes</li>
-<li>(Analysis FPS: &lt;blank&gt;)</li>
-<li>(Maximum FPS: &lt;blank&gt;)</li>
-<li>(Alarm Maximum FPS: &lt;blank&gt;)</li>
+<li>Mode: Constant</li>
+<li>Encoder: Raw Record Stream</li>
+<li>Max record time: 900 (15 minutes)</li>
 </ul></li>
-<li>In the "Source" tab, specify:
+<li>Storage:
 <ul class="org-ul">
-<li>Src path: rtsp://user:gobbledygook@new.small.private.:554/11</li>
-<li>(Method: TCP)</li>
-<li>(Target colorspace: 32 bit colour)</li>
-<li>Capture Resolution: 1920x1080 1080p</li>
-</ul></li>
-<li>In the "Timestamp" tab, specify:
+<li>Location: <i>DVR/AgentDVR</i></li>
+<li>Folder: Outside</li>
+<li>Storage Management:
 <ul class="org-ul">
-<li>Timestamp Label X: 10</li>
-<li>Timestamp Label Y: 10</li>
-<li>Font Size: Large</li>
+<li>On: yes</li>
+<li>Max Size: 0 (unlimited)</li>
+<li>Max Age: 168 (7 days)</li>
 </ul></li>
-<li>In the "Buffers" tab, specify:
-<ul class="org-ul">
-<li>Image Buffer Size (frames): 40</li>
 </ul></li>
 </ul>
 </div>
@@ -2929,8 +2482,8 @@ machine simply by adding it to the <code>tvrs</code> group.
 </p>
 </div>
 </div>
-<div id="outline-container-orge3238f6" class="outline-3">
-<h3 id="orge3238f6"><span class="section-number-3">9.3.</span> Include Abbey Variables</h3>
+<div id="outline-container-org3e1f87c" class="outline-3">
+<h3 id="org3e1f87c"><span class="section-number-3">9.3.</span> Include Abbey Variables</h3>
 <div class="outline-text-3" id="text-9-3">
 <p>
 Private variables in <q>private/vars-abbey.yml</q> are needed, as in the
@@ -3476,7 +3029,7 @@ the list of "inputs" available in a postal code typically ends with
 the OTA (over the air) broadcasts.
 </p>
 
-<pre class="example" id="org82b52c7">
+<pre class="example" id="orgd6730f4">
 $ tv_grab_zz_sdjson --configure --config-file .mythtv/Mr.Antenna.xml
 Cache file for lineups, schedules and programs.
 Cache file: [/home/mythtv/.xmltv/tv_grab_zz_sdjson.cache]
@@ -3897,9 +3450,6 @@ except the roles are found in <q>Institute/roles/</q> as well as <q>roles/</q>.
         kamino:
         kessel:
         ord-mantell:
-    weather:
-      hosts:
-        anoat:
     dvrs:
       hosts:
         dantooine:
@@ -3953,10 +3503,6 @@ institutional roles, then the liturgical roles.
   hosts: campus
   roles: [ campus, abbey-cloister ]
 
-- name: Configure Weather
-  hosts: weather
-  roles: [ abbey-weather ]
-
 - name: Configure DVRs
   hosts: dvrs
   roles: [ abbey-dvr ]
@@ -4184,10 +3730,10 @@ operating system version of all abbey managed machines.
 <p>
 The abbey changes location almost weekly, so its timezone changes
 occasionally.  Droplet does not move.  Gate and other simple servers
-(the weather monitors) are kept in UTC.  Core, the DVRs, TVRs, and the
-desktops all want updating to the current local timezone.  The
-desktops are managed maually, but the rest can all be updated using
-Ansible.
+are kept in UTC.  Core, the DVRs, TVRs, Home Assistant and the
+desktops all want updating to the current local timezone.  Home
+Assistant and the desktops are managed maually, but the rest can all
+be updated using Ansible.
 </p>
 
 <p>
@@ -4228,12 +3774,11 @@ last host in the previous play.
 
 - hosts: dvrs
   tasks:
-  - name: Restart Zoneminder.
+  - name: Restart AgentDVR.
     become: yes
     systemd:
-      service: <span class="org-string">"{{ item }}"</span>
+      service: AgentDVR
       state: restarted
-    loop: [ mysql, zoneminder ]
     when: new_tz.changed
 
 - hosts: tvrs
@@ -4986,7 +4531,7 @@ to <q>private/db.campus_vpn</q>.)
 </div>
 <div id="postamble" class="status">
 <p class="author">Author: Matt Birkholz</p>
-<p class="date">Created: 2024-09-03 Tue 08:46</p>
+<p class="date">Created: 2024-09-20 Fri 13:28</p>
 <p class="validation"><a href="https://validator.w3.org/check?uri=referer">Validate</a></p>
 </div>
 </body>