From 0c24ea37318cf339d2a657878e821b4f602d4e60 Mon Sep 17 00:00:00 2001 From: Matt Birkholz <matt@birchwood-abbey.net> Date: Fri, 20 Sep 2024 13:28:32 -0600 Subject: [PATCH] Update README.html. --- README.html | 1103 +++++++++++++++------------------------------------ 1 file changed, 324 insertions(+), 779 deletions(-) diff --git a/README.html b/README.html index e7eabb9..2def347 100644 --- a/README.html +++ b/README.html @@ -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: > 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 ] && /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: > 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… 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 & 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">">>$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">">>$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> < $<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> < 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> & 127; - $<span class="org-variable-name">status</span> >>= 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>->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>->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>->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>->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 <(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 - & 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: >- - 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 < /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…" 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: <blank>)</li> -<li>(Maximum FPS: <blank>)</li> -<li>(Alarm Maximum FPS: <blank>)</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> -- 2.25.1