Update README.html.
authorMatt Birkholz <matt@birchwood-abbey.net>
Sun, 15 Jun 2025 18:24:13 +0000 (12:24 -0600)
committerMatt Birkholz <matt@birchwood-abbey.net>
Sun, 15 Jun 2025 18:24:13 +0000 (12:24 -0600)
README.html

index 6d8b64865c355e00c5676b37f59a9d524ae1865c..188d5047310df8df0ab8527d9192781acf760a37 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>
-<!-- 2025-05-31 Sat 22:27 -->
+<!-- 2025-06-15 Sun 12:23 -->
 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <title>A Small Institute</title>
@@ -24,8 +24,8 @@ an expendable public face (easily wiped clean) while maintaining a
 secure and private campus that can function with or without the
 Internet.
 </p>
-<div id="outline-container-org12986b7" class="outline-2">
-<h2 id="org12986b7"><span class="section-number-2">1.</span> Overview</h2>
+<div id="outline-container-orga8e230f" class="outline-2">
+<h2 id="orga8e230f"><span class="section-number-2">1.</span> Overview</h2>
 <div class="outline-text-2" id="text-1">
 <p>
 This small institute has a public server on the Internet, Front, that
@@ -48,7 +48,7 @@ connects to Front making the institute email, cloud, etc. available to
 members off campus.
 </p>
 
-<pre class="example" id="org9ebcfa4">
+<pre class="example" id="orga1e7f29">
                 =                                                   
               _|||_                                                 
         =-The-Institute-=                                           
@@ -83,7 +83,7 @@ desktops.  When off campus, members access institute resources via the
 VPN on Front (via hotel Wi-Fi).  When <i>on</i> campus, members can use the
 much faster and always available (despite Internet connectivity
 issues) VPN on Gate (via campus Wi-Fi).  A member's Android phones and
-devices can use the same Wi-Fis, VPNs (via the OpenVPN app) and
+devices can use the same Wi-Fis, VPNs (via the WireGuard™ app) and
 services.  On a desktop or by phone, at home or abroad, members can
 access their email and the institute's private web and cloud.
 </p>
@@ -95,8 +95,8 @@ uses OpenPGP encryption to secure message content.
 </p>
 </div>
 </div>
-<div id="outline-container-org68fcf87" class="outline-2">
-<h2 id="org68fcf87"><span class="section-number-2">2.</span> Caveats</h2>
+<div id="outline-container-org0a30205" class="outline-2">
+<h2 id="org0a30205"><span class="section-number-2">2.</span> Caveats</h2>
 <div class="outline-text-2" id="text-2">
 <p>
 This small institute prizes its privacy, so there is little or no
@@ -144,8 +144,8 @@ month) because of this assumption.
 </p>
 </div>
 </div>
-<div id="outline-container-org1120238" class="outline-2">
-<h2 id="org1120238"><span class="section-number-2">3.</span> The Services</h2>
+<div id="outline-container-orgb3ec65d" class="outline-2">
+<h2 id="orgb3ec65d"><span class="section-number-2">3.</span> The Services</h2>
 <div class="outline-text-2" id="text-3">
 <p>
 The small institute's network is designed to provide a number of
@@ -157,8 +157,8 @@ policies.  On first reading, those subsections should be skipped; they
 reference particulars first introduced in the following chapter.
 </p>
 </div>
-<div id="outline-container-org189ccc3" class="outline-3">
-<h3 id="org189ccc3"><span class="section-number-3">3.1.</span> The Name Service</h3>
+<div id="outline-container-org7a5fce0" class="outline-3">
+<h3 id="org7a5fce0"><span class="section-number-3">3.1.</span> The Name Service</h3>
 <div class="outline-text-3" id="text-3-1">
 <p>
 The institute has a public domain, e.g. <code>small.example.org</code>, and a
@@ -172,8 +172,8 @@ names like <code>core</code>.
 </p>
 </div>
 </div>
-<div id="outline-container-org73635f2" class="outline-3">
-<h3 id="org73635f2"><span class="section-number-3">3.2.</span> The Email Service</h3>
+<div id="outline-container-org474e9f9" class="outline-3">
+<h3 id="org474e9f9"><span class="section-number-3">3.2.</span> The Email Service</h3>
 <div class="outline-text-3" id="text-3-2">
 <p>
 Front provides the public SMTP (Simple Mail Transfer Protocol) service
@@ -247,8 +247,8 @@ setting for the maximum message size is given in a code block labeled
 configurations wherever <code>&lt;&lt;postfix-message-size&gt;&gt;</code> appears.
 </p>
 </div>
-<div id="outline-container-org630ebd0" class="outline-4">
-<h4 id="org630ebd0"><span class="section-number-4">3.2.1.</span> The Postfix Configurations</h4>
+<div id="outline-container-org234f92e" class="outline-4">
+<h4 id="org234f92e"><span class="section-number-4">3.2.1.</span> The Postfix Configurations</h4>
 <div class="outline-text-4" id="text-3-2-1">
 <p>
 The institute aims to accommodate encrypted email containing short
@@ -263,7 +263,7 @@ handle maxi-messages.
 </p>
 
 <div class="org-src-container">
-<code>postfix-message-size</code><pre class="src src-conf" id="org76e6614"><code>- { p: message_size_limit, v: 104857600 }
+<code>postfix-message-size</code><pre class="src src-conf" id="orgd79c252"><code>- { p: message_size_limit, v: 104857600 }
 </code></pre>
 </div>
 
@@ -278,7 +278,7 @@ re-sending the bounce (or just grabbing the go-bag!).
 </p>
 
 <div class="org-src-container">
-<code>postfix-queue-times</code><pre class="src src-conf" id="orge1f6b9f"><code>- { p: delay_warning_time, v: 1h }
+<code>postfix-queue-times</code><pre class="src src-conf" id="org65c9d39"><code>- { p: delay_warning_time, v: 1h }
 - { p: maximal_queue_lifetime, v: 4h }
 - { p: bounce_queue_lifetime, v: 4h }
 </code></pre>
@@ -292,7 +292,7 @@ disables relaying (other than for the local networks).
 </p>
 
 <div class="org-src-container">
-<code>postfix-relaying</code><pre class="src src-conf" id="orgfc41c7f"><code>- p: smtpd_relay_restrictions
+<code>postfix-relaying</code><pre class="src src-conf" id="orga9d70d2"><code>- p: smtpd_relay_restrictions
   v: permit_mynetworks reject_unauth_destination
 </code></pre>
 </div>
@@ -304,7 +304,7 @@ effect.
 </p>
 
 <div class="org-src-container">
-<code>postfix-maildir</code><pre class="src src-conf" id="org38b410e"><code>- { p: home_mailbox, v: Maildir/ }
+<code>postfix-maildir</code><pre class="src src-conf" id="org709078f"><code>- { p: home_mailbox, v: Maildir/ }
 </code></pre>
 </div>
 
@@ -315,8 +315,8 @@ in the respective roles below.
 </p>
 </div>
 </div>
-<div id="outline-container-org4c351b3" class="outline-4">
-<h4 id="org4c351b3"><span class="section-number-4">3.2.2.</span> The Dovecot Configurations</h4>
+<div id="outline-container-org9345774" class="outline-4">
+<h4 id="org9345774"><span class="section-number-4">3.2.2.</span> The Dovecot Configurations</h4>
 <div class="outline-text-4" id="text-3-2-2">
 <p>
 The Dovecot settings on both Front and Core disable POP and require
@@ -330,7 +330,7 @@ The official documentation for Dovecot once was a Wiki but now is
 </p>
 
 <div class="org-src-container">
-<code>dovecot-tls</code><pre class="src src-conf" id="org9aa23d4"><code><span class="org-variable-name">protocols</span> = imap
+<code>dovecot-tls</code><pre class="src src-conf" id="orgfcc2fc9"><code><span class="org-variable-name">protocols</span> = imap
 <span class="org-variable-name">ssl</span> = required
 </code></pre>
 </div>
@@ -342,7 +342,7 @@ configuration keeps them from even listening at the IMAP port
 </p>
 
 <div class="org-src-container">
-<code>dovecot-ports</code><pre class="src src-conf" id="orgf5fbf4b"><code><span class="org-type">service imap-login</span> {
+<code>dovecot-ports</code><pre class="src src-conf" id="org19ccc28"><code><span class="org-type">service imap-login</span> {
   <span class="org-type">inet_listener imap</span> {
     <span class="org-variable-name">port</span> = 0
   }
@@ -356,7 +356,7 @@ directories.
 </p>
 
 <div class="org-src-container">
-<code>dovecot-maildir</code><pre class="src src-conf" id="org4ab71c8"><code><span class="org-variable-name">mail_location</span> = maildir:~/Maildir
+<code>dovecot-maildir</code><pre class="src src-conf" id="org45ed273"><code><span class="org-variable-name">mail_location</span> = maildir:~/Maildir
 </code></pre>
 </div>
 
@@ -368,15 +368,15 @@ common settings with host specific settings for <code>ssl_cert</code> and
 </div>
 </div>
 </div>
-<div id="outline-container-orgf659a90" class="outline-3">
-<h3 id="orgf659a90"><span class="section-number-3">3.3.</span> The Web Services</h3>
+<div id="outline-container-org4e9f4f9" class="outline-3">
+<h3 id="org4e9f4f9"><span class="section-number-3">3.3.</span> The Web Services</h3>
 <div class="outline-text-3" id="text-3-3">
 <p>
 Front provides the public HTTP service that serves institute web pages
 at e.g. <code>https://small.example.org/</code>.  The small institute initially
 runs with a self-signed, "snake oil" server certificate, causing
 browsers to warn of possible fraud, but this certificate is easily
-replaced by one signed by a recognized authority, as discussed in <a href="#org1024107">The
+replaced by one signed by a recognized authority, as discussed in <a href="#orgd6c5c74">The
 Front Role</a>.
 </p>
 
@@ -431,15 +431,15 @@ will automatically wipe it within 15 minutes.
 </p>
 </div>
 </div>
-<div id="outline-container-orgd0834d2" class="outline-3">
-<h3 id="orgd0834d2"><span class="section-number-3">3.4.</span> The Cloud Service</h3>
+<div id="outline-container-org582454b" class="outline-3">
+<h3 id="org582454b"><span class="section-number-3">3.4.</span> The Cloud Service</h3>
 <div class="outline-text-3" id="text-3-4">
 <p>
 Core runs Nextcloud to provide a private institute cloud at
 <code>https://core.small.private/nextcloud/</code>.  It is managed manually per
 <a href="https://docs.nextcloud.com/server/latest/admin_manual/">The Nextcloud Server Administration Guide</a>.  The code <i>and</i> data,
 including especially database dumps, are stored in <q>/Nextcloud/</q> which
-is included in Core's backup procedure as described in <a href="#org2a7965d">Backups</a>.  The
+is included in Core's backup procedure as described in <a href="#orgd6409ab">Backups</a>.  The
 default Apache2 configuration expects to find the web scripts in
 <q>/var/www/nextcloud/</q>, so the institute symbolically links this to
 <q>/Nextcloud/nextcloud/</q>.
@@ -453,130 +453,15 @@ private network.
 </p>
 </div>
 </div>
-<div id="outline-container-org1ea6412" class="outline-3">
-<h3 id="org1ea6412"><span class="section-number-3">3.5.</span> The VPN Services</h3>
+<div id="outline-container-org03cae01" class="outline-3">
+<h3 id="org03cae01"><span class="section-number-3">3.5.</span> Accounts</h3>
 <div class="outline-text-3" id="text-3-5">
 <p>
-The institute's public and campus VPNs have many common configuration
-options that are discussed here.  These are included, with example
-certificates and network addresses, in the complete server
-configurations of <a href="#org1024107">The Front Role</a> and <a href="#org936487d">The Gate Role</a>, as well as the
-matching client configurations in <a href="#orge6d718e">The Core Role</a> and the <q>.ovpn</q> files
-generated by <a href="#orgdc36b90">The Client Command</a>.  The configurations are based on the
-documentation for OpenVPN v2.4: the <code>openvpn(8)</code> manual page and <a href="https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/">this
-web page</a>.
-</p>
-</div>
-<div id="outline-container-orgf621905" class="outline-4">
-<h4 id="orgf621905"><span class="section-number-4">3.5.1.</span> The VPN Configuration Options</h4>
-<div class="outline-text-4" id="text-3-5-1">
-<p>
-The institute VPNs use UDP on a subnet topology (rather than
-point-to-point) with "split tunneling".  The UDP support accommodates
-real-time, connection-less protocols.  The split tunneling is for
-efficiency with frontier bandwidth.  The subnet topology, with the
-<code>client-to-client</code> option, allows members to "talk" to each other on
-the VPN subnets using any (experimental) protocol.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-dev-mode</code><pre class="src src-conf" id="org2722342"><code>dev-type tun
-dev ovpn
-topology subnet
-client-to-client
-</code></pre>
-</div>
-
-<p>
-A <code>keepalive</code> option is included on the servers so that clients detect
-an unreachable server and reset the TLS session.  The option's default
-is doubled to 2 minutes out of respect for frontier service
-interruptions.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-keepalive</code><pre class="src src-conf" id="orgfe45128"><code>keepalive 10 120
-</code></pre>
-</div>
-
-<p>
-As mentioned in <a href="#org189ccc3">The Name Service</a>, the institute uses a campus name
-server.  OpenVPN is instructed to push its address and the campus
-search domain.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-dns</code><pre class="src src-conf" id="org035983f"><code>push <span class="org-string">"dhcp-option DOMAIN {{ domain_priv }}"</span>
-push <span class="org-string">"dhcp-option DNS {{ core_addr }}"</span>
-</code></pre>
-</div>
-
-<p>
-The institute does not put the OpenVPN server in a <code>chroot</code> jail, but
-it does drop privileges to run as user <code>nobody:nobody</code>.  The
-<code>persist-</code> options are needed because <code>nobody</code> cannot open the tunnel
-device nor the key files.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-drop-priv</code><pre class="src src-conf" id="orge4d440d"><code>user nobody
-group nogroup
-persist-key
-persist-tun
-</code></pre>
-</div>
-
-<p>
-The institute does a little additional hardening, sacrificing some
-compatibility with out-of-date clients.  Such clients are generally
-frowned upon at the institute.  Here <code>cipher</code> is set to <code>AES-256-GCM</code>,
-the default for OpenVPN v2.4, and <code>auth</code> is upped to <code>SHA256</code> from
-<code>SHA1</code>.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-crypt</code><pre class="src src-conf" id="orgb13e625"><code>cipher AES-256-GCM
-auth SHA256
-</code></pre>
-</div>
-
-<p>
-Finally, a <code>max-client</code> limit was chosen to frustrate flooding while
-accommodating a few members with a handful of devices each.
-</p>
-
-<div class="org-src-container">
-<code>openvpn-max</code><pre class="src src-conf" id="orgd79815a"><code>max-clients 20
-</code></pre>
-</div>
-
-<p>
-The institute's servers are lightly loaded so a few debugging options
-are appropriate.  To help recognize host addresses in the logs, and
-support direct client-to-client communication, host IP addresses are
-made "persistent" in the <q>ipp.txt</q> file.  The server's status is
-periodically written to the <q>openvpn-status.log</q> and verbosity is
-raised from the default level 1 to level 3 (just short of a deluge).
-</p>
-
-<div class="org-src-container">
-<code>openvpn-debug</code><pre class="src src-conf" id="org59b4726"><code>ifconfig-pool-persist ipp.txt
-status openvpn-status.log
-verb 3
-</code></pre>
-</div>
-</div>
-</div>
-</div>
-<div id="outline-container-orga38c5ed" class="outline-3">
-<h3 id="orga38c5ed"><span class="section-number-3">3.6.</span> Accounts</h3>
-<div class="outline-text-3" id="text-3-6">
-<p>
 A small institute has just a handful of members.  For simplicity (and
 thus security) static configuration files are preferred over complex
 account management systems, LDAP, Active Directory, and the like.  The
 Ansible scripts configure the same set of user accounts on Core and
-Front.  <a href="#org5db480b">The Institute Commands</a> (e.g. <code>./inst new dick</code>) capture the
+Front.  <a href="#org1eb4766">The Institute Commands</a> (e.g. <code>./inst new dick</code>) capture the
 processes of enrolling, modifying and retiring members of the
 institute.  They update the administrator's membership roll, and run
 Ansible to create (and disable) accounts on Core, Front, Nextcloud,
@@ -591,9 +476,9 @@ accomplished via the campus cloud and the resulting desktop files can
 all be private (readable and writable only by the owner) by default.
 </p>
 </div>
-<div id="outline-container-org4a8c2dc" class="outline-4">
-<h4 id="org4a8c2dc"><span class="section-number-4">3.6.1.</span> The Administration Accounts</h4>
-<div class="outline-text-4" id="text-3-6-1">
+<div id="outline-container-org43f41c7" class="outline-4">
+<h4 id="org43f41c7"><span class="section-number-4">3.5.1.</span> The Administration Accounts</h4>
+<div class="outline-text-4" id="text-3-5-1">
 <p>
 The institute avoids the use of the <code>root</code> account (<code>uid 0</code>) because
 it is exempt from the normal Unix permissions checking.  The <code>sudo</code>
@@ -601,22 +486,22 @@ command is used to consciously (conscientiously!) run specific scripts
 and programs as <code>root</code>.  When installation of a Debian OS leaves the
 host with no user accounts, just the <code>root</code> account, the next step is
 to create a system administrator's account named <code>sysadm</code> and to give
-it permission to use the <code>sudo</code> command (e.g. as described in <a href="#org9282df0">The
+it permission to use the <code>sudo</code> command (e.g. as described in <a href="#orgd887559">The
 Front Machine</a>).  When installation prompts for the name of an
 initial, privileged user account the same name is given (e.g. as
-described in <a href="#orgae4ce75">The Core Machine</a>).  Installation may <i>not</i> prompt and
+described in <a href="#org935c0f2">The Core Machine</a>).  Installation may <i>not</i> prompt and
 still create an initial user account with a distribution specific name
 (e.g. <code>pi</code>).  Any name can be used as long as it is provided as the
 value of <code>ansible_user</code> in <a href="hosts"><q>hosts</q></a>.  Its password is specified by a
 vault-encrypted variable in the <a href="Secret/become.yml"><q>Secret/become.yml</q></a> file.  (The
-<a href="hosts"><q>hosts</q></a> and <a href="Secret/become.yml"><q>Secret/become.yml</q></a> files are described in <a href="#orgda31017">The Ansible
+<a href="hosts"><q>hosts</q></a> and <a href="Secret/become.yml"><q>Secret/become.yml</q></a> files are described in <a href="#org9259070">The Ansible
 Configuration</a>.)
 </p>
 </div>
 </div>
-<div id="outline-container-org9470e9b" class="outline-4">
-<h4 id="org9470e9b"><span class="section-number-4">3.6.2.</span> The Monkey Accounts</h4>
-<div class="outline-text-4" id="text-3-6-2">
+<div id="outline-container-orgbc62415" class="outline-4">
+<h4 id="orgbc62415"><span class="section-number-4">3.5.2.</span> The Monkey Accounts</h4>
+<div class="outline-text-4" id="text-3-5-2">
 <p>
 The institute's Core uses a special account named <code>monkey</code> to run
 background jobs with limited privileges.  One of those jobs is to keep
@@ -626,9 +511,9 @@ account is created on Front as well.
 </div>
 </div>
 </div>
-<div id="outline-container-org1c1be46" class="outline-3">
-<h3 id="org1c1be46"><span class="section-number-3">3.7.</span> Keys</h3>
-<div class="outline-text-3" id="text-3-7">
+<div id="outline-container-org8244014" class="outline-3">
+<h3 id="org8244014"><span class="section-number-3">3.6.</span> Keys</h3>
+<div class="outline-text-3" id="text-3-6">
 <p>
 The institute keeps its "master secrets" in an encrypted
 volume on an off-line hard drive, e.g. a LUKS (Linux Unified Key
@@ -658,36 +543,26 @@ Front's.)</dd>
 </dl>
 
 <p>
-The institute uses a number of X.509 certificates to authenticate VPN
-clients and servers.  They are created by the EasyRSA Certificate
-Authority stored in <a href="Secret/CA/"><q>Secret/CA/</q></a>.
+The institute uses a couple X.509 certificates to authenticate
+servers.  They are created by the EasyRSA Certificate Authority stored
+in <a href="Secret/CA/"><q>Secret/CA/</q></a>.
 </p>
 
 <dl class="org-dl">
 <dt><a href="Secret/CA/pki/ca.crt"><q>Secret/CA/pki/ca.crt</q></a></dt><dd>The institute CA certificate, used to
 sign the other certificates.</dd>
 
-<dt><a href="Secret/CA/pki/issued/small.example.org.crt"><q>Secret/CA/pki/issued/small.example.org.crt</q></a></dt><dd>The public Apache,
-Postfix, and OpenVPN servers on Front.</dd>
-
-<dt><a href="Secret/CA/pki/issued/gate.small.private.crt"><q>Secret/CA/pki/issued/gate.small.private.crt</q></a></dt><dd>The campus
-OpenVPN server on Gate.</dd>
+<dt><a href="Secret/CA/pki/issued/small.example.org.crt"><q>Secret/CA/pki/issued/small.example.org.crt</q></a></dt><dd>The public
+Postfix, Dovecot and Apache servers on Front.</dd>
 
 <dt><a href="Secret/CA/pki/issued/core.small.private.crt"><q>Secret/CA/pki/issued/core.small.private.crt</q></a></dt><dd>The campus
-Apache (thus Nextcloud), and Dovecot-IMAPd servers.</dd>
-
-<dt><a href="Secret/CA/pki/issued/core.crt"><q>Secret/CA/pki/issued/core.crt</q></a></dt><dd>Core's client certificate, by
-which it authenticates to Front.</dd>
+Postfix, Dovecot and Apache (thus Nextcloud) servers on Core.</dd>
 </dl>
 
 <p>
-The <code>./inst client</code> command creates client certificates and keys, and
-can generate OpenVPN configuration (<q>.ovpn</q>) files for Android and
-Debian.  The command updates the institute membership roll, requiring
-the member's username, keeping a list of the member's clients (in case
-all authorizations need to be revoked quickly).  The list of client
-certificates that have been revoked is stored along with the
-membership roll (in <q>private/members.yml</q> as the value of <code>revoked</code>).
+The <code>./inst client</code> command updates the institute membership roll,
+which lists members and their clients' public keys, and is stored in
+<q>private/members.yml</q>.
 </p>
 
 <p>
@@ -705,8 +580,8 @@ e.g. <code>root@core.small.private</code>.</dd>
 
 <p>
 The institute administrator updates a couple encrypted copies of this
-drive after enrolling new members, changing a password, issuing VPN
-credentials, etc.
+drive after enrolling new members, changing a password,
+(de)authorizing a VPN client, etc.
 </p>
 
 <pre class="example">
@@ -722,9 +597,9 @@ the administrator's password keep, to install a new SSH key.
 </p>
 </div>
 </div>
-<div id="outline-container-org2a7965d" class="outline-3">
-<h3 id="org2a7965d"><span class="section-number-3">3.8.</span> Backups</h3>
-<div class="outline-text-3" id="text-3-8">
+<div id="outline-container-orgd6409ab" class="outline-3">
+<h3 id="orgd6409ab"><span class="section-number-3">3.7.</span> Backups</h3>
+<div class="outline-text-3" id="text-3-7">
 <p>
 The small institute backs up its data, but not so much so that nothing
 can be deleted.  It actually mirrors user directories (<q>/home/</q>), the
@@ -759,7 +634,7 @@ files mentioned in the Nextcloud database dump).
 </p>
 
 <div class="org-src-container">
-<a href="private/backup"><q>private/backup</q></a><pre class="src src-sh" id="org8d148e8"><code><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span><span class="org-comment"> -e</span>
+<a href="private/backup"><q>private/backup</q></a><pre class="src src-sh" id="orgf388eed"><code><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span><span class="org-comment"> -e</span>
 <span class="org-comment-delimiter">#</span>
 <span class="org-comment-delimiter"># </span><span class="org-comment">DO NOT EDIT.  Maintained (will be replaced) by Ansible.</span>
 <span class="org-comment-delimiter">#</span>
@@ -861,8 +736,8 @@ finish
 </div>
 </div>
 </div>
-<div id="outline-container-orgb4f5586" class="outline-2">
-<h2 id="orgb4f5586"><span class="section-number-2">4.</span> The Particulars</h2>
+<div id="outline-container-org77f802b" class="outline-2">
+<h2 id="org77f802b"><span class="section-number-2">4.</span> The Particulars</h2>
 <div class="outline-text-2" id="text-4">
 <p>
 This chapter introduces Ansible variables intended to simplify
@@ -874,13 +749,13 @@ stored in separate files: <a href="public/vars.yml"><q>public/vars.yml</q></a> a
 
 <p>
 The example settings in this document configure VirtualBox VMs as
-described in the <a href="#org7832c43">Testing</a> chapter.  For more information about how a
+described in the <a href="#org3d2b7a1">Testing</a> chapter.  For more information about how a
 small institute turns the example Ansible code into a working Ansible
-configuration, see chapter <a href="#orgda31017">The Ansible Configuration</a>.
+configuration, see chapter <a href="#org9259070">The Ansible Configuration</a>.
 </p>
 </div>
-<div id="outline-container-org0ba16f9" class="outline-3">
-<h3 id="org0ba16f9"><span class="section-number-3">4.1.</span> Generic Particulars</h3>
+<div id="outline-container-org31c2f38" class="outline-3">
+<h3 id="org31c2f38"><span class="section-number-3">4.1.</span> Generic Particulars</h3>
 <div class="outline-text-3" id="text-4-1">
 <p>
 The small institute's domain name is used quite frequently in the
@@ -919,22 +794,22 @@ domain_priv: small.private
 </div>
 </div>
 </div>
-<div id="outline-container-org29f9840" class="outline-3">
-<h3 id="org29f9840"><span class="section-number-3">4.2.</span> Subnets</h3>
+<div id="outline-container-org31e3a27" class="outline-3">
+<h3 id="org31e3a27"><span class="section-number-3">4.2.</span> Subnets</h3>
 <div class="outline-text-3" id="text-4-2">
 <p>
-The small institute uses a private Ethernet, two VPNs, and an
-untrusted Ethernet (for the campus Wi-Fi access point).  Each must
-have a unique private network address.  Hosts using the VPNs are also
-using foreign private networks, e.g. a notebook on a hotel Wi-Fi.  To
-better the chances that all of these networks get unique addresses,
-the small institute uses addresses in the IANA's (Internet Assigned
-Numbers Authority's) private network address ranges <i>except</i> the
-<code>192.168</code> address range already in widespread use.  This still leaves
-69,632 8 bit networks (each addressing up to 254 hosts) from which to
-choose.  The following table lists their CIDRs (subnet numbers in
-Classless Inter-Domain Routing notation) in abbreviated form (eliding
-69,624 rows).
+The small institute uses a private Ethernet, two VPNs, and a "wild",
+untrusted Ethernet for the campus Wi-Fi access point(s) and wired IoT
+appliances.  Each must have a unique private network address.  Hosts
+using the VPNs are also using foreign private networks, e.g. a
+notebook on a hotel Wi-Fi.  To better the chances that all of these
+networks get unique addresses, the small institute uses addresses in
+the IANA's (Internet Assigned Numbers Authority's) private network
+address ranges <i>except</i> the <code>192.168</code> address range already in
+widespread use.  This still leaves 69,632 8 bit networks (each
+addressing up to 254 hosts) from which to choose.  The following table
+lists their CIDRs (subnet numbers in Classless Inter-Domain Routing
+notation) in abbreviated form (eliding 69,624 rows).
 </p>
 
 <table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
@@ -1020,7 +895,7 @@ example result follows the code.
 </code></pre>
 </div>
 
-<div class="TEXT" id="org078b489">
+<div class="TEXT" id="org829cf98">
 <p>
 =&gt; 10.62.17.0/24
 </p>
@@ -1033,21 +908,21 @@ code block below.  The small institute treats these addresses as
 sensitive information so again the code block below "tangles" into
 <a href="private/vars.yml"><q>private/vars.yml</q></a> rather than <a href="public/vars.yml"><q>public/vars.yml</q></a>.  Two of the
 addresses are in <code>192.168</code> subnets because they are part of a test
-configuration using mostly-default VirtualBoxes (described <a href="#org7832c43">here</a>).
+configuration using mostly-default VirtualBoxes (described <a href="#org3d2b7a1">here</a>).
 </p>
 
 <div class="org-src-container">
 <a href="private/vars.yml"><q>private/vars.yml</q></a><pre class="src src-conf"><code>
 private_net_cidr:           192.168.56.0/24
 wild_net_cidr:              192.168.57.0/24
-public_vpn_net_cidr:        10.177.86.0/24
-campus_vpn_net_cidr:        10.84.138.0/24
+public_wg_net_cidr:         10.177.87.0/24
+campus_wg_net_cidr:         10.84.139.0/24
 </code></pre>
 </div>
 
 <p>
 The network addresses are needed in several additional formats, e.g.
-network address and subnet mask (<code>10.84.138.0 255.255.255.0</code>).  The
+network address and subnet mask (<code>10.84.139.0 255.255.255.0</code>).  The
 following boilerplate uses Ansible's <code>ipaddr</code> filter to set several
 corresponding variables, each with an appropriate suffix,
 e.g. <code>_net_and_mask</code> rather than <code>_net_cidr</code>.
@@ -1059,24 +934,24 @@ e.g. <code>_net_and_mask</code> rather than <code>_net_cidr</code>.
 private_net_mask:
            <span class="org-string">"{{ private_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
 private_net_and_mask:      <span class="org-string">"{{ private_net }} {{ private_net_mask }}"</span>
-public_vpn_net:
-        <span class="org-string">"{{ public_vpn_net_cidr | ansible.utils.ipaddr('network') }}"</span>
-public_vpn_net_mask:
-        <span class="org-string">"{{ public_vpn_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
-public_vpn_net_and_mask:
-                     <span class="org-string">"{{ public_vpn_net }} {{ public_vpn_net_mask }}"</span>
-campus_vpn_net:
-        <span class="org-string">"{{ campus_vpn_net_cidr | ansible.utils.ipaddr('network') }}"</span>
-campus_vpn_net_mask:
-        <span class="org-string">"{{ campus_vpn_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
-campus_vpn_net_and_mask:
-                     <span class="org-string">"{{ campus_vpn_net }} {{ campus_vpn_net_mask }}"</span>
 wild_net:     <span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('network') }}"</span>
 wild_net_mask:
               <span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
 wild_net_and_mask:               <span class="org-string">"{{ wild_net }} {{ wild_net_mask }}"</span>
 wild_net_broadcast:
             <span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('broadcast') }}"</span>
+public_wg_net:
+         <span class="org-string">"{{ public_wg_net_cidr | ansible.utils.ipaddr('network') }}"</span>
+public_wg_net_mask:
+         <span class="org-string">"{{ public_wg_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
+public_wg_net_and_mask:
+                       <span class="org-string">"{{ public_wg_net }} {{ public_wg_net_mask }}"</span>
+campus_wg_net:
+         <span class="org-string">"{{ campus_wg_net_cidr | ansible.utils.ipaddr('network') }}"</span>
+campus_wg_net_mask:
+         <span class="org-string">"{{ campus_wg_net_cidr | ansible.utils.ipaddr('netmask') }}"</span>
+campus_wg_net_and_mask:
+    <span class="org-string">"{{ campus_wg_net }} {{ campus_wg_net_mask }}"</span>
 </code></pre>
 </div>
 
@@ -1101,14 +976,13 @@ virtual machines and networks, and the VirtualBox user manual uses
 </p>
 
 <p>
-Finally, five host addresses are needed frequently in the Ansible
+Finally, four host addresses are needed frequently in the Ansible
 code.  The first two are Core's and Gate's addresses on the private
-Ethernet.  The next two are Gate's and the campus Wi-Fi's addresses on
-the "wild" subnet, the untrusted Ethernet (<code>wild_net</code>) between Gate
-and the campus Wi-Fi access point(s) and IoT appliances.  The last is
-Front's address on the public VPN, perversely called
-<code>front_private_addr</code>.  The following code block picks the obvious IP
-addresses for Core (host 1) and Gate (host 2).
+Ethernet.  The other two are Gate's and the campus Wi-Fi's addresses
+on the wild Ethernet.  The following code block chooses host 1 for
+Core and host 2 for Gate on the private Ethernet.  On the wild
+Ethernet, host 1 is Gate and host 2 is the access point (or wired
+IoT appliance).
 </p>
 
 <div class="org-src-container">
@@ -1116,36 +990,33 @@ addresses for Core (host 1) and Gate (host 2).
 gate_addr_cidr:  <span class="org-string">"{{ private_net_cidr | ansible.utils.ipaddr('2') }}"</span>
 gate_wild_addr_cidr:
                     <span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('1') }}"</span>
-wifi_wan_addr_cidr: <span class="org-string">"{{ wild_net_cidr | ansible.utils.ipaddr('2') }}"</span>
-front_private_addr_cidr:
-              <span class="org-string">"{{ public_vpn_net_cidr | ansible.utils.ipaddr('1') }}"</span>
+front_wg_addr_cidr:
+               <span class="org-string">"{{ public_wg_net_cidr | ansible.utils.ipaddr('1') }}"</span>
 
 core_addr:   <span class="org-string">"{{ core_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
 gate_addr:   <span class="org-string">"{{ gate_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
 gate_wild_addr:
         <span class="org-string">"{{ gate_wild_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
-wifi_wan_addr:
-         <span class="org-string">"{{ wifi_wan_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
-front_private_addr:
-    <span class="org-string">"{{ front_private_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
+front_wg_addr:
+         <span class="org-string">"{{ front_wg_addr_cidr | ansible.utils.ipaddr('address') }}"</span>
 </code></pre>
 </div>
 </div>
 </div>
 </div>
-<div id="outline-container-orgbf29081" class="outline-2">
-<h2 id="orgbf29081"><span class="section-number-2">5.</span> The Hardware</h2>
+<div id="outline-container-org5af003d" class="outline-2">
+<h2 id="org5af003d"><span class="section-number-2">5.</span> The Hardware</h2>
 <div class="outline-text-2" id="text-5">
 <p>
 The small institute's network was built by its system administrator
 using Ansible on a trusted notebook.  The Ansible configuration and
 scripts were generated by "tangling" the Ansible code included here.
-(<a href="#orgda31017">The Ansible Configuration</a> describes how to do this.)  The following
+(<a href="#org9259070">The Ansible Configuration</a> describes how to do this.)  The following
 sections describe how Front, Gate and Core were prepared for Ansible.
 </p>
 </div>
-<div id="outline-container-org9282df0" class="outline-3">
-<h3 id="org9282df0"><span class="section-number-3">5.1.</span> The Front Machine</h3>
+<div id="outline-container-orgd887559" class="outline-3">
+<h3 id="orgd887559"><span class="section-number-3">5.1.</span> The Front Machine</h3>
 <div class="outline-text-3" id="text-5-1">
 <p>
 Front is the small institute's public facing server, a virtual machine
@@ -1158,8 +1029,8 @@ possible to quickly re-provision a new Front machine from a frontier
 Internet café using just the administrator's notebook.
 </p>
 </div>
-<div id="outline-container-org7dbbd56" class="outline-4">
-<h4 id="org7dbbd56"><span class="section-number-4">5.1.1.</span> A Digital Ocean Droplet</h4>
+<div id="outline-container-orgcc4ff46" class="outline-4">
+<h4 id="orgcc4ff46"><span class="section-number-4">5.1.1.</span> A Digital Ocean Droplet</h4>
 <div class="outline-text-4" id="text-5-1-1">
 <p>
 The following example prepared a new front on a Digital Ocean droplet.
@@ -1183,7 +1054,7 @@ root@ubuntu#
 <p>
 The freshly created Digital Ocean droplet came with just one account,
 <code>root</code>, but the small institute avoids remote access to the "super
-user" account (per the policy in <a href="#org4a8c2dc">The Administration Accounts</a>), so the
+user" account (per the policy in <a href="#org43f41c7">The Administration Accounts</a>), so the
 administrator created a <code>sysadm</code> account with the ability to request
 escalated privileges via the <code>sudo</code> command.
 </p>
@@ -1206,7 +1077,7 @@ notebook$
 The password was generated by <code>gpw</code>, saved in the administrator's
 password keep, and later added to <a href="Secret/become.yml"><q>Secret/become.yml</q></a> as shown below.
 (Producing a working Ansible configuration with <a href="Secret/become.yml"><q>Secret/become.yml</q></a>
-file is described in <a href="#orgda31017">The Ansible Configuration</a>.)
+file is described in <a href="#org9259070">The Ansible Configuration</a>.)
 </p>
 
 <pre class="example">
@@ -1220,7 +1091,7 @@ notebook_     &gt;&gt;Secret/become.yml
 <p>
 After creating the <code>sysadm</code> account on the droplet, the administrator
 concatenated a personal public ssh key and the key found in
-<a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#org3fe9e37">The CA Command</a>) into an <q>admin_keys</q>
+<a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#orgd14a881">The CA Command</a>) into an <q>admin_keys</q>
 file, copied it to the droplet, and installed it as the
 <q>authorized_keys</q> for <code>sysadm</code>.
 </p>
@@ -1252,7 +1123,8 @@ in <q>Secret/ssh_front/</q> to the droplet and restarted the SSH server.
 </p>
 
 <pre class="example">
-notebook$ scp Secret/ssh_front/etc/ssh/ssh_host_* sysadm@159.65.75.60:
+notebook$ ( cd Secret/ssh_front/etc/ssh/;
+notebook_   scp ssh_host_* sysadm@159.65.75.60: )
 notebook$ ssh sysadm@159.65.75.60
 sysadm@ubuntu$ chmod 600 ssh_host_*
 sysadm@ubuntu$ chmod 644 ssh_host_*.pub
@@ -1303,8 +1175,8 @@ address.
 </div>
 </div>
 </div>
-<div id="outline-container-orgae4ce75" class="outline-3">
-<h3 id="orgae4ce75"><span class="section-number-3">5.2.</span> The Core Machine</h3>
+<div id="outline-container-org935c0f2" class="outline-3">
+<h3 id="org935c0f2"><span class="section-number-3">5.2.</span> The Core Machine</h3>
 <div class="outline-text-3" id="text-5-2">
 <p>
 Core is the small institute's private file, email, cloud and whatnot
@@ -1328,7 +1200,7 @@ The following example prepared a new core on a PC with Debian 11
 freshly installed.  During installation, the machine was named <code>core</code>,
 no desktop or server software was installed, no root password was set,
 and a privileged account named <code>sysadm</code> was created (per the policy in
-<a href="#org4a8c2dc">The Administration Accounts</a>).
+<a href="#org43f41c7">The Administration Accounts</a>).
 </p>
 
 <pre class="example">
@@ -1344,7 +1216,7 @@ Is the information correct? [Y/n]
 The password was generated by <code>gpw</code>, saved in the administrator's
 password keep, and later added to <a href="Secret/become.yml"><q>Secret/become.yml</q></a> as shown below.
 (Producing a working Ansible configuration with <a href="Secret/become.yml"><q>Secret/become.yml</q></a>
-file is described in <a href="#orgda31017">The Ansible Configuration</a>.)
+file is described in <a href="#org9259070">The Ansible Configuration</a>.)
 </p>
 
 <pre class="example">
@@ -1363,7 +1235,7 @@ modem and installed them as shown below.
 
 <pre class="example">
 $ sudo apt install netplan.io systemd-resolved unattended-upgrades \
-_                  ntp isc-dhcp-server bind9 apache2 openvpn \
+_                  ntp isc-dhcp-server bind9 apache2 wireguard \
 _                  postfix dovecot-imapd fetchmail expect rsync \
 _                  gnupg openssh-server
 </pre>
@@ -1392,7 +1264,7 @@ _                  nagios-nrpe-plugin
 
 <p>
 Next, the administrator concatenated a personal public ssh key and the
-key found in <a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#org3fe9e37">The CA Command</a>) into an
+key found in <a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#orgd14a881">The CA Command</a>) into an
 <q>admin_keys</q> file, copied it to Core, and installed it as the
 <q>authorized_keys</q> for <code>sysadm</code>.
 </p>
@@ -1432,7 +1304,7 @@ a new, private IP address and a default route.
 <p>
 In the example command lines below, the address <code>10.227.248.1</code> was
 generated by the random subnet address picking procedure described in
-<a href="#org29f9840">Subnets</a>, and is named <code>core_addr</code> in the Ansible code.  The second
+<a href="#org31e3a27">Subnets</a>, and is named <code>core_addr</code> in the Ansible code.  The second
 address, <code>10.227.248.2</code>, is the corresponding address for Gate's
 Ethernet interface, and is named <code>gate_addr</code> in the Ansible
 code.
@@ -1448,8 +1320,8 @@ At this point Core was ready for provisioning with Ansible.
 </p>
 </div>
 </div>
-<div id="outline-container-org40481f7" class="outline-3">
-<h3 id="org40481f7"><span class="section-number-3">5.3.</span> The Gate Machine</h3>
+<div id="outline-container-org817fd99" class="outline-3">
+<h3 id="org817fd99"><span class="section-number-3">5.3.</span> The Gate Machine</h3>
 <div class="outline-text-3" id="text-5-3">
 <p>
 Gate is the small institute's route to the Internet, and the campus
@@ -1470,7 +1342,7 @@ USB-Ethernet adapter, or a wireless adapter connected to a
 campground Wi-Fi access point, etc.</li>
 </ol>
 
-<pre class="example" id="orgf49687b">
+<pre class="example" id="orgaf88c7e">
 =============== | ==================================================
                 |                                           Premises
           (Campus ISP)                                              
@@ -1483,8 +1355,8 @@ campground Wi-Fi access point, etc.</li>
                 +----Ethernet switch                                
 </pre>
 </div>
-<div id="outline-container-org6edd1eb" class="outline-4">
-<h4 id="org6edd1eb"><span class="section-number-4">5.3.1.</span> Alternate Gate Topology</h4>
+<div id="outline-container-org528e565" class="outline-4">
+<h4 id="org528e565"><span class="section-number-4">5.3.1.</span> Alternate Gate Topology</h4>
 <div class="outline-text-4" id="text-5-3-1">
 <p>
 While Gate and Core really need to be separate machines for security
@@ -1493,7 +1365,7 @@ This avoids the need for a second Wi-Fi access point and leads to the
 following topology.
 </p>
 
-<pre class="example" id="org0d82eb6">
+<pre class="example" id="org80ac5eb">
 =============== | ==================================================
                 |                                           Premises
            (House ISP)                                              
@@ -1517,12 +1389,12 @@ its Ethernet and Wi-Fi clients are allowed to communicate).
 </p>
 </div>
 </div>
-<div id="outline-container-orgaa27474" class="outline-4">
-<h4 id="orgaa27474"><span class="section-number-4">5.3.2.</span> Original Gate Topology</h4>
+<div id="outline-container-orge82f877" class="outline-4">
+<h4 id="orge82f877"><span class="section-number-4">5.3.2.</span> Original Gate Topology</h4>
 <div class="outline-text-4" id="text-5-3-2">
 <p>
 The Ansible code in this document is somewhat dependent on the
-physical network shown in the <a href="#org12986b7">Overview</a> wherein Gate has three network
+physical network shown in the <a href="#orga8e230f">Overview</a> wherein Gate has three network
 interfaces.
 </p>
 
@@ -1531,7 +1403,7 @@ The following example prepared a new gate on a PC with Debian 11
 freshly installed.  During installation, the machine was named <code>gate</code>,
 no desktop or server software was installed, no root password was set,
 and a privileged account named <code>sysadm</code> was created (per the policy in
-<a href="#org4a8c2dc">The Administration Accounts</a>).
+<a href="#org43f41c7">The Administration Accounts</a>).
 </p>
 
 <pre class="example">
@@ -1547,7 +1419,7 @@ Is the information correct? [Y/n]
 The password was generated by <code>gpw</code>, saved in the administrator's
 password keep, and later added to <a href="Secret/become.yml"><q>Secret/become.yml</q></a> as shown below.
 (Producing a working Ansible configuration with <a href="Secret/become.yml"><q>Secret/become.yml</q></a>
-file is described in <a href="#orgda31017">The Ansible Configuration</a>.)
+file is described in <a href="#org9259070">The Ansible Configuration</a>.)
 </p>
 
 <pre class="example">
@@ -1566,13 +1438,13 @@ cable modem and installed them as shown below.
 
 <pre class="example">
 $ sudo apt install netplan.io systemd-resolved unattended-upgrades \
-_                  ufw isc-dhcp-server postfix openvpn \
+_                  ufw isc-dhcp-server postfix wireguard \
 _                  openssh-server
 </pre>
 
 <p>
 Next, the administrator concatenated a personal public ssh key and the
-key found in <a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#org3fe9e37">The CA Command</a>) into an
+key found in <a href="Secret/ssh_admin/"><q>Secret/ssh_admin/</q></a> (created by <a href="#orgd14a881">The CA Command</a>) into an
 <q>admin_keys</q> file, copied it to Gate, and installed it as the
 <q>authorized_keys</q> for <code>sysadm</code>.
 </p>
@@ -1612,7 +1484,7 @@ a new, private IP address.
 <p>
 In the example command lines below, the address <code>10.227.248.2</code> was
 generated by the random subnet address picking procedure described in
-<a href="#org29f9840">Subnets</a>, and is named <code>gate_addr</code> in the Ansible code.
+<a href="#org31e3a27">Subnets</a>, and is named <code>gate_addr</code> in the Ansible code.
 </p>
 
 <pre class="example">
@@ -1624,7 +1496,7 @@ Gate was also connected to the USB Ethernet dongles cabled to the
 campus Wi-Fi access point and the campus ISP.  The three network
 adapters are known by their MAC addresses, the values of the variables
 <code>gate_lan_mac</code>, <code>gate_wild_mac</code>, and <code>gate_isp_mac</code>.  (For more
-information, see the Gate role's <a href="#orgfd4af3f">Configure Netplan</a> task.)
+information, see the Gate role's <a href="#org2c03bc2">Configure Netplan</a> task.)
 </p>
 
 <p>
@@ -1634,28 +1506,28 @@ At this point Gate was ready for provisioning with Ansible.
 </div>
 </div>
 </div>
-<div id="outline-container-org72438bf" class="outline-2">
-<h2 id="org72438bf"><span class="section-number-2">6.</span> The All Role</h2>
+<div id="outline-container-org550bf9d" class="outline-2">
+<h2 id="org550bf9d"><span class="section-number-2">6.</span> The All Role</h2>
 <div class="outline-text-2" id="text-6">
 <p>
 The <code>all</code> role contains tasks that are executed on all of the
 institute's servers.  At the moment there is just the one.
 </p>
 </div>
-<div id="outline-container-org204ceac" class="outline-3">
-<h3 id="org204ceac"><span class="section-number-3">6.1.</span> Include Particulars</h3>
+<div id="outline-container-org5f5eca0" class="outline-3">
+<h3 id="org5f5eca0"><span class="section-number-3">6.1.</span> Include Particulars</h3>
 <div class="outline-text-3" id="text-6-1">
 <p>
 The <code>all</code> role's task contains a reference to a common institute
 particular, the institute's <code>domain_name</code>, a variable found in the
 <q>public/vars.yml</q> file.  Thus the first task of the <code>all</code> role is to
-include the variables defined in this file (described in <a href="#orgb4f5586">The
+include the variables defined in this file (described in <a href="#org77f802b">The
 Particulars</a>).  The code block below is the first to tangle into
-<a href="roles/front/tasks/main.yml"><q>roles/all/tasks/main.yml</q></a>.
+<a href="roles/all/tasks/main.yml"><q>roles/all/tasks/main.yml</q></a>.
 </p>
 
 <div class="org-src-container">
-<a href="roles/front/tasks/main.yml"><q>roles/all/tasks/main.yml</q></a><pre class="src src-conf"><code>---
+<a href="roles/all/tasks/main.yml"><q>roles/all/tasks/main.yml</q></a><pre class="src src-conf"><code>---
 - name: Include public variables.
   include_vars: ../public/vars.yml
   tags: accounts
@@ -1663,8 +1535,8 @@ Particulars</a>).  The code block below is the first to tangle into
 </div>
 </div>
 </div>
-<div id="outline-container-orgbfaf303" class="outline-3">
-<h3 id="orgbfaf303"><span class="section-number-3">6.2.</span> Enable Systemd Resolved</h3>
+<div id="outline-container-org4bb7687" class="outline-3">
+<h3 id="org4bb7687"><span class="section-number-3">6.2.</span> Enable Systemd Resolved</h3>
 <div class="outline-text-3" id="text-6-2">
 <p>
 The <code>systemd-networkd</code> and <code>systemd-resolved</code> service units are not
@@ -1685,7 +1557,7 @@ follows these recommendations (and <i>not</i> the suggestion to enable
 </p>
 
 <div class="org-src-container">
-<a href="roles_t/front/tasks/main.yml"><q>roles_t/all/tasks/main.yml</q></a><pre class="src src-conf"><code>
+<a href="roles_t/all/tasks/main.yml"><q>roles_t/all/tasks/main.yml</q></a><pre class="src src-conf"><code>
 - name: Install systemd-resolved.
   become: yes
   <span class="org-variable-name">apt: pkg</span>=systemd-resolved
@@ -1721,18 +1593,18 @@ follows these recommendations (and <i>not</i> the suggestion to enable
 </div>
 </div>
 </div>
-<div id="outline-container-org2756611" class="outline-3">
-<h3 id="org2756611"><span class="section-number-3">6.3.</span> Trust Institute Certificate Authority</h3>
+<div id="outline-container-org0e4f2a5" class="outline-3">
+<h3 id="org0e4f2a5"><span class="section-number-3">6.3.</span> Trust Institute Certificate Authority</h3>
 <div class="outline-text-3" id="text-6-3">
 <p>
 All servers should recognize the institute's Certificate Authority as
 trustworthy, so its certificate is added to the set of trusted CAs on
 each host.  More information about how the small institute manages its
-X.509 certificates is available in <a href="#org1c1be46">Keys</a>.
+X.509 certificates is available in <a href="#org8244014">Keys</a>.
 </p>
 
 <div class="org-src-container">
-<a href="roles_t/front/tasks/main.yml"><q>roles_t/all/tasks/main.yml</q></a><pre class="src src-conf"><code>
+<a href="roles_t/all/tasks/main.yml"><q>roles_t/all/tasks/main.yml</q></a><pre class="src src-conf"><code>
 - name: Trust the institute CA.
   become: yes
   copy:
@@ -1746,7 +1618,7 @@ X.509 certificates is available in <a href="#org1c1be46">Keys</a>.
 </div>
 
 <div class="org-src-container">
-<a href="roles_t/front/handlers/main.yml"><q>roles_t/all/handlers/main.yml</q></a><pre class="src src-conf"><code>
+<a href="roles_t/all/handlers/main.yml"><q>roles_t/all/handlers/main.yml</q></a><pre class="src src-conf"><code>
 - name: Update CAs.
   become: yes
   command: update-ca-certificates
@@ -1755,15 +1627,15 @@ X.509 certificates is available in <a href="#org1c1be46">Keys</a>.
 </div>
 </div>
 </div>
-<div id="outline-container-org1024107" class="outline-2">
-<h2 id="org1024107"><span class="section-number-2">7.</span> The Front Role</h2>
+<div id="outline-container-orgd6c5c74" class="outline-2">
+<h2 id="orgd6c5c74"><span class="section-number-2">7.</span> The Front Role</h2>
 <div class="outline-text-2" id="text-7">
 <p>
 The <code>front</code> role installs and configures the services expected on the
 institute's publicly accessible "front door": email, web, VPN.  The
 virtual machine is prepared with an Ubuntu Server install and remote
 access to a privileged, administrator's account.  (For details, see
-<a href="#org9282df0">The Front Machine</a>.)
+<a href="#orgd887559">The Front Machine</a>.)
 </p>
 
 <p>
@@ -1777,18 +1649,12 @@ replacing the "snake oil" is as easy as replacing these two files,
 perhaps with symbolic links to, for example,
 <q>/etc/letsencrypt/live/small.example.org/fullchain.pem</q>.
 </p>
-
-<p>
-Note that the OpenVPN server does <i>not</i> use <q>/etc/server.crt</q>.  It
-uses the institute's CA and server certificates, and expects client
-certificates signed by the institute CA.
-</p>
 </div>
-<div id="outline-container-org92fa9ca" class="outline-3">
-<h3 id="org92fa9ca"><span class="section-number-3">7.1.</span> Include Particulars</h3>
+<div id="outline-container-org4dc0d2c" class="outline-3">
+<h3 id="org4dc0d2c"><span class="section-number-3">7.1.</span> Include Particulars</h3>
 <div class="outline-text-3" id="text-7-1">
 <p>
-The first task, as in <a href="#org72438bf">The All Role</a>, is to include the institute
+The first task, as in <a href="#org550bf9d">The All Role</a>, is to include the institute
 particulars.  The <code>front</code> role refers to private variables and the
 membership roll, so these are included was well.
 </p>
@@ -1810,8 +1676,8 @@ membership roll, so these are included was well.
 </div>
 </div>
 </div>
-<div id="outline-container-orgd1223b4" class="outline-3">
-<h3 id="orgd1223b4"><span class="section-number-3">7.2.</span> Configure Hostname</h3>
+<div id="outline-container-orgca8551e" class="outline-3">
+<h3 id="orgca8551e"><span class="section-number-3">7.2.</span> Configure Hostname</h3>
 <div class="outline-text-3" id="text-7-2">
 <p>
 This task ensures that Front's <q>/etc/hostname</q> and <q>/etc/mailname</q> are
@@ -1841,8 +1707,8 @@ delivery.
 </div>
 </div>
 </div>
-<div id="outline-container-orgc60729b" class="outline-3">
-<h3 id="orgc60729b"><span class="section-number-3">7.3.</span> Add Administrator to System Groups</h3>
+<div id="outline-container-org29ab388" class="outline-3">
+<h3 id="org29ab388"><span class="section-number-3">7.3.</span> Add Administrator to System Groups</h3>
 <div class="outline-text-3" id="text-7-3">
 <p>
 The administrator often needs to read (directories of) log files owned
@@ -1862,8 +1728,8 @@ these groups speeds up debugging.
 </div>
 </div>
 </div>
-<div id="outline-container-org581d004" class="outline-3">
-<h3 id="org581d004"><span class="section-number-3">7.4.</span> Configure SSH</h3>
+<div id="outline-container-org6e30fce" class="outline-3">
+<h3 id="org6e30fce"><span class="section-number-3">7.4.</span> Configure SSH</h3>
 <div class="outline-text-3" id="text-7-4">
 <p>
 The SSH service on Front needs to be known to Monkey.  The following
@@ -1901,8 +1767,8 @@ those stored in <a href="Secret/ssh_front/etc/ssh/"><q>Secret/ssh_front/etc/ssh/
 </div>
 </div>
 </div>
-<div id="outline-container-org345d680" class="outline-3">
-<h3 id="org345d680"><span class="section-number-3">7.5.</span> Configure Monkey</h3>
+<div id="outline-container-org5256a97" class="outline-3">
+<h3 id="org5256a97"><span class="section-number-3">7.5.</span> Configure Monkey</h3>
 <div class="outline-text-3" id="text-7-5">
 <p>
 The small institute runs cron jobs and web scripts that generate
@@ -1910,7 +1776,7 @@ reports and perform checks.  The un-privileged jobs are run by a
 system account named <code>monkey</code>.  One of Monkey's more important jobs on
 Core is to run <code>rsync</code> to update the public web site on Front.  Monkey
 on Core will login as <code>monkey</code> on Front to synchronize the files (as
-described in <a href="#orgbcda1ce">*Configure Apache2</a>).  To do that without needing a
+described in <a href="#org40128e7">*Configure Apache2</a>).  To do that without needing a
 password, the <code>monkey</code> account on Front should authorize Monkey's SSH
 key on Core.
 </p>
@@ -1942,8 +1808,8 @@ key on Core.
 </div>
 </div>
 </div>
-<div id="outline-container-orgce756fc" class="outline-3">
-<h3 id="orgce756fc"><span class="section-number-3">7.6.</span> Install Rsync</h3>
+<div id="outline-container-orgdcab48f" class="outline-3">
+<h3 id="orgdcab48f"><span class="section-number-3">7.6.</span> Install Rsync</h3>
 <div class="outline-text-3" id="text-7-6">
 <p>
 Monkey uses Rsync to keep the institute's public web site up-to-date.
@@ -1958,8 +1824,8 @@ Monkey uses Rsync to keep the institute's public web site up-to-date.
 </div>
 </div>
 </div>
-<div id="outline-container-org0ec2b68" class="outline-3">
-<h3 id="org0ec2b68"><span class="section-number-3">7.7.</span> Install Unattended Upgrades</h3>
+<div id="outline-container-orgb5ea472" class="outline-3">
+<h3 id="orgb5ea472"><span class="section-number-3">7.7.</span> Install Unattended Upgrades</h3>
 <div class="outline-text-3" id="text-7-7">
 <p>
 The institute prefers to install security updates as soon as possible.
@@ -1974,13 +1840,13 @@ The institute prefers to install security updates as soon as possible.
 </div>
 </div>
 </div>
-<div id="outline-container-orgdd1398a" class="outline-3">
-<h3 id="orgdd1398a"><span class="section-number-3">7.8.</span> Configure User Accounts</h3>
+<div id="outline-container-org9f70d16" class="outline-3">
+<h3 id="org9f70d16"><span class="section-number-3">7.8.</span> Configure User Accounts</h3>
 <div class="outline-text-3" id="text-7-8">
 <p>
 User accounts are created immediately so that Postfix and Dovecot can
 start delivering email immediately, <i>without</i> returning "no such
-recipient" replies.  The <a href="#org6138e49">Account Management</a> chapter describes the
+recipient" replies.  The <a href="#org944d8f7">Account Management</a> chapter describes the
 <code>members</code> and <code>usernames</code> variables used below.
 </p>
 
@@ -2018,8 +1884,8 @@ recipient" replies.  The <a href="#org6138e49">Account Management</a> chapter de
 </div>
 </div>
 </div>
-<div id="outline-container-org7ce2595" class="outline-3">
-<h3 id="org7ce2595"><span class="section-number-3">7.9.</span> Install Server Certificate</h3>
+<div id="outline-container-orgc4b9bdf" class="outline-3">
+<h3 id="orgc4b9bdf"><span class="section-number-3">7.9.</span> Install Server Certificate</h3>
 <div class="outline-text-3" id="text-7-9">
 <p>
 The servers on Front use the same certificate (and key) to
@@ -2049,8 +1915,8 @@ readable by <code>root</code>.
 </div>
 </div>
 </div>
-<div id="outline-container-orga330953" class="outline-3">
-<h3 id="orga330953"><span class="section-number-3">7.10.</span> Configure Postfix on Front</h3>
+<div id="outline-container-org1237c08" class="outline-3">
+<h3 id="org1237c08"><span class="section-number-3">7.10.</span> Configure Postfix on Front</h3>
 <div class="outline-text-3" id="text-7-10">
 <p>
 Front uses Postfix to provide the institute's public SMTP service, and
@@ -2067,7 +1933,7 @@ The appropriate answers are listed here but will be checked
 </ul>
 
 <p>
-As discussed in <a href="#org73635f2">The Email Service</a> above, Front's Postfix configuration
+As discussed in <a href="#org474e9f9">The Email Service</a> above, Front's Postfix configuration
 includes site-wide support for larger message sizes, shorter queue
 times, the relaying configuration, and the common path to incoming
 emails.  These and a few Front-specific Postfix configurations
@@ -2075,14 +1941,14 @@ settings make up the complete configuration (below).
 </p>
 
 <p>
-Front relays messages from the institute's public VPN via which Core
-relays messages from the campus.
+Front relays messages from the institute's public WireGuard™ subnet
+via which Core relays messages from the campus.
 </p>
 
 <div class="org-src-container">
-<code>postfix-front-networks</code><pre class="src src-conf" id="orgba644bd"><code>- p: mynetworks
+<code>postfix-front-networks</code><pre class="src src-conf" id="org18244e0"><code>- p: mynetworks
   v: &gt;-
-     {{ public_vpn_net_cidr }}
+     {{ public_wg_net_cidr }}
      127.0.0.0/8
      [<span class="org-type">::ffff:127.0.0.0</span>]/104
      [<span class="org-type">::1</span>]/128
@@ -2096,7 +1962,7 @@ difficult for internal hosts, who do <i>not</i> have (public) domain names.
 </p>
 
 <div class="org-src-container">
-<code>postfix-front-restrictions</code><pre class="src src-conf" id="orga288f25"><code>- p: smtpd_recipient_restrictions
+<code>postfix-front-restrictions</code><pre class="src src-conf" id="orge3d5cde"><code>- p: smtpd_recipient_restrictions
   v: &gt;-
      permit_mynetworks
      reject_unauth_pipelining
@@ -2117,13 +1983,13 @@ messages; incoming messages are delivered locally, without
 </p>
 
 <div class="org-src-container">
-<code>postfix-header-checks</code><pre class="src src-conf" id="orga20329d"><code>- p: smtp_header_checks
+<code>postfix-header-checks</code><pre class="src src-conf" id="orge624ce5"><code>- p: smtp_header_checks
   v: regexp:/etc/postfix/header_checks.cf
 </code></pre>
 </div>
 
 <div class="org-src-container">
-<code>postfix-header-checks-content</code><pre class="src src-conf" id="org0057dfe"><code>/^Received:/    IGNORE
+<code>postfix-header-checks-content</code><pre class="src src-conf" id="org63fc398"><code>/^Received:/    IGNORE
 /^User-Agent:/  IGNORE
 </code></pre>
 </div>
@@ -2135,7 +2001,7 @@ Debian default for <code>inet_interfaces</code>.
 </p>
 
 <div class="org-src-container">
-<code>postfix-front</code><pre class="src src-conf" id="org5ee702d"><code>- { p: smtpd_tls_cert_file, v: /etc/server.crt }
+<code>postfix-front</code><pre class="src src-conf" id="org591d058"><code>- { p: smtpd_tls_cert_file, v: /etc/server.crt }
 - { p: smtpd_tls_key_file, v: /etc/server.key }
 &lt;&lt;postfix-front-networks&gt;&gt;
 &lt;&lt;postfix-front-restrictions&gt;&gt;
@@ -2204,8 +2070,8 @@ start and enable the service.
 </div>
 </div>
 </div>
-<div id="outline-container-org75a0395" class="outline-3">
-<h3 id="org75a0395"><span class="section-number-3">7.11.</span> Configure Public Email Aliases</h3>
+<div id="outline-container-org901d745" class="outline-3">
+<h3 id="org901d745"><span class="section-number-3">7.11.</span> Configure Public Email Aliases</h3>
 <div class="outline-text-3" id="text-7-11">
 <p>
 The institute's Front needs to deliver email addressed to a number of
@@ -2229,7 +2095,7 @@ created by a more specialized role.
         abuse:          root
         webmaster:      root
         admin:          root
-        monkey:         monkey@{{ front_private_addr }}
+        monkey:         monkey@{{ front_wg_addr }}
         root:           {{ ansible_user }}
     path: /etc/aliases
     marker: <span class="org-string">"# {mark} INSTITUTE MANAGED BLOCK"</span>
@@ -2246,8 +2112,8 @@ created by a more specialized role.
 </div>
 </div>
 </div>
-<div id="outline-container-orgdc00207" class="outline-3">
-<h3 id="orgdc00207"><span class="section-number-3">7.12.</span> Configure Dovecot IMAPd</h3>
+<div id="outline-container-org44abb98" class="outline-3">
+<h3 id="org44abb98"><span class="section-number-3">7.12.</span> Configure Dovecot IMAPd</h3>
 <div class="outline-text-3" id="text-7-12">
 <p>
 Front uses Dovecot's IMAPd to allow user Fetchmail jobs on Core to
@@ -2256,7 +2122,7 @@ default with POP and IMAP (without TLS) support disabled.  This is a
 bit "over the top" given that Core accesses Front via VPN, but helps
 to ensure privacy even when members must, in extremis, access recent
 email directly from their accounts on Front.  For more information
-about Front's role in the institute's email services, see <a href="#org73635f2">The Email
+about Front's role in the institute's email services, see <a href="#org474e9f9">The Email
 Service</a>.
 </p>
 
@@ -2312,8 +2178,8 @@ and enables it to start at every reboot.
 </div>
 </div>
 </div>
-<div id="outline-container-orga7d4870" class="outline-3">
-<h3 id="orga7d4870"><span class="section-number-3">7.13.</span> Configure Apache2 <a id="orgbcda1ce"></a></h3>
+<div id="outline-container-orgd0433e4" class="outline-3">
+<h3 id="orgd0433e4"><span class="section-number-3">7.13.</span> Configure Apache2 <a id="org40128e7"></a></h3>
 <div class="outline-text-3" id="text-7-13">
 <p>
 This is the small institute's public web site.  It is simple, static,
@@ -2349,7 +2215,7 @@ taken from <a href="https://www.ssllabs.com/projects/best-practices">https://www
 </p>
 
 <div class="org-src-container">
-<code>apache-ciphers</code><pre class="src src-conf" id="orgd32683d"><code>SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
+<code>apache-ciphers</code><pre class="src src-conf" id="orgbd8c682"><code>SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
 SSLHonorCipherOrder on
 <span class="org-type">SSLCipherSuite {</span>{ [ <span class="org-string">'ECDHE-ECDSA-AES128-GCM-SHA256'</span>,
                     <span class="org-string">'ECDHE-ECDSA-AES256-GCM-SHA384'</span>,
@@ -2404,7 +2270,7 @@ used on all of the institute's web sites.
 </p>
 
 <div class="org-src-container">
-<code>apache-userdir-front</code><pre class="src src-conf" id="org5949197"><code>UserDir /home/www-users
+<code>apache-userdir-front</code><pre class="src src-conf" id="orgf8d4a88"><code>UserDir /home/www-users
 &lt;Directory /home/www-users/&gt;
         Require all granted
         AllowOverride None
@@ -2419,7 +2285,7 @@ HTTPS URLs.
 </p>
 
 <div class="org-src-container">
-<code>apache-redirect-front</code><pre class="src src-conf" id="org8203d34"><code>&lt;VirtualHost *:80&gt;
+<code>apache-redirect-front</code><pre class="src src-conf" id="orgf20dd06"><code>&lt;VirtualHost *:80&gt;
         Redirect permanent / https://{{ domain_name }}/
 &lt;/VirtualHost&gt;
 </code></pre>
@@ -2444,7 +2310,7 @@ the inside of a <code>VirtualHost</code> block.  They should apply globally.
 </p>
 
 <div class="org-src-container">
-<code>apache-front</code><pre class="src src-conf" id="org26265cf"><code>ServerName {{ domain_name }}
+<code>apache-front</code><pre class="src src-conf" id="org5b2dff3"><code>ServerName {{ domain_name }}
 ServerAdmin webmaster@{{ domain_name }}
 
 DocumentRoot /home/www
@@ -2603,147 +2469,96 @@ the users' <q>~/Public/HTML/</q> directories.
 </div>
 </div>
 </div>
-<div id="outline-container-org3bc00f9" class="outline-3">
-<h3 id="org3bc00f9"><span class="section-number-3">7.14.</span> Configure OpenVPN</h3>
+<div id="outline-container-org290050a" class="outline-3">
+<h3 id="org290050a"><span class="section-number-3">7.14.</span> Configure Public WireGuard™ Subnet</h3>
 <div class="outline-text-3" id="text-7-14">
 <p>
-Front uses OpenVPN to provide the institute's public VPN service.  The
-configuration is straightforward with one complication.  OpenVPN needs
-to know how to route to the campus VPN, which is only accessible when
-Core is connected.  OpenVPN supports these dynamic routes internally
-with client-specific configuration files.  The small institute uses
-one of these, <q>/etc/openvpn/ccd/core</q>, so that OpenVPN will know to
-route packets for the campus networks to Core.
+Front uses WireGuard™ to provide a public (Internet accessible) VPN
+service.  Core has an interface on this VPN and is expected to forward
+packets between it and the institute's other private networks.
 </p>
 
-<div class="org-src-container">
-<code>openvpn-ccd-core</code><pre class="src src-conf" id="orgbbdd4f4"><code>iroute {{ private_net_and_mask }}
-iroute {{ campus_vpn_net_and_mask }}
-</code></pre>
-</div>
-
 <p>
-The VPN clients are <i>not</i> configured to route <i>all</i> of their traffic
-through the VPN, so Front pushes routes to the other institute
-networks.  The clients thus know to route traffic for the private
-Ethernet or campus VPN to Front on the public VPN.  (If the clients
-<i>were</i> configured to route all traffic through the VPN, the one
-default route is all that would be needed.)  Front itself is in the
-same situation, outside the institute networks with a default route
-through some ISP, and thus needs the same routes as the clients.
+The following example <a href="#orgeeaba66"><q>private/front-wg0.conf</q></a> configuration recognizes
+Core by its public key and routes the institute's private networks to
+it.  It also recognizes Dick's notebook and his (replacement) phone,
+assigning them host numbers 4 and 6 on the VPN.
 </p>
 
 <div class="org-src-container">
-<code>openvpn-front-routes</code><pre class="src src-conf" id="org9e2d873"><code>route {{ private_net_and_mask }}
-route {{ campus_vpn_net_and_mask }}
-push <span class="org-string">"route {{ private_net_and_mask }}"</span>
-push <span class="org-string">"route {{ campus_vpn_net_and_mask }}"</span>
+<q>private/front-wg0.conf</q><pre class="src src-conf" id="orgeeaba66"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.177.87.1/24
+<span class="org-variable-name">ListenPort</span> = 39608
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+<span class="org-variable-name">PostUp</span> = resolvectl dns %i 192.168.56.1
+<span class="org-variable-name">PostUp</span> = resolvectl domain %i small.private
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Core</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = lGhC51IBgZtlq4H2bsYFuKvPtV0VAEwUvVIn5fW7D0c=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.2
+<span class="org-variable-name">AllowedIPs</span> = 192.168.56.0/24
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.0/24
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">dick</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = 4qd4xdRztZBKhFrX9jI/b4fnMzpKQ5qhg691hwYSsX8=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.4
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">dicks-razr</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = zho0qMxoLclJSQu4GeJEcMkk0hx4Q047OcNc8vOejVw=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.6
 </code></pre>
 </div>
 
 <p>
-The complete OpenVPN configuration for Front includes a <code>server</code>
-option, the <code>client-config-dir</code> option, the routes mentioned above,
-and the common options discussed in <a href="#org1ea6412">The VPN Service</a>.
+The configuration used on Dick's notebook when it is abroad looks like
+this:
 </p>
 
 <div class="org-src-container">
-<code>openvpn-front</code><pre class="src src-conf" id="org069808e"><code>server {{ public_vpn_net_and_mask }}
-client-config-dir /etc/openvpn/ccd
-&lt;&lt;openvpn-front-routes&gt;&gt;
-&lt;&lt;openvpn-dev-mode&gt;&gt;
-&lt;&lt;openvpn-keepalive&gt;&gt;
-&lt;&lt;openvpn-dns&gt;&gt;
-&lt;&lt;openvpn-drop-priv&gt;&gt;
-&lt;&lt;openvpn-crypt&gt;&gt;
-&lt;&lt;openvpn-max&gt;&gt;
-&lt;&lt;openvpn-debug&gt;&gt;
-ca /usr/local/share/ca-certificates/{{ domain_name }}.crt
-cert server.crt
-key server.key
-dh dh2048.pem
-tls-crypt shared.key
+WireGuard™ tunnel on Dick's notebook, used abroad<pre class="src src-conf"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.177.87.3
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+<span class="org-variable-name">PostUp</span> = resolvectl dns %i 192.168.56.1
+<span class="org-variable-name">PostUp</span> = resolvectl domain %i small.private
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Front</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = S+6HaTnOwwhWgUGXjSBcPAvifKw+j8BDTRfq534gNW4=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.1
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.0/24
+<span class="org-variable-name">AllowedIPs</span> = 192.168.56.0/24
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.0/24
 </code></pre>
 </div>
 
 <p>
-Finally, here are the tasks (and handler) required to install and
-configure the OpenVPN server on Front.
+The following tasks install WireGuard™, configure it with
+<a href="#orgeeaba66"><q>private/front-wg0.conf</q></a>, and enable the service.
 </p>
 
 <div class="org-src-container">
 <a href="roles_t/front/tasks/main.yml"><q>roles_t/front/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Install OpenVPN.
-  become: yes
-  <span class="org-variable-name">apt: pkg</span>=openvpn
-
-- name: Enable IP forwarding.
-  become: yes
-  sysctl:
-    name: net.ipv4.ip_forward
-    value: <span class="org-string">"1"</span>
-    state: present
-
-- name: Create OpenVPN client configuration directory.
-  become: yes
-  file:
-    path: /etc/openvpn/ccd
-    state: directory
-  notify: Restart OpenVPN.
-
-- name: Install OpenVPN client configuration for Core.
-  become: yes
-  copy:
-    content: |
-      &lt;&lt;openvpn-ccd-core&gt;&gt;
-    dest: /etc/openvpn/ccd/core
-  notify: Restart OpenVPN.
-
-- name: Disable former VPN clients.
+- name: Install WireGuard&#8482;.
   become: yes
-  copy:
-    content: <span class="org-string">"disable\n"</span>
-    dest: /etc/openvpn/ccd/{{ item }}
-  loop: <span class="org-string">"{{ revoked }}"</span>
-  tags: accounts
+  <span class="org-variable-name">apt: pkg</span>=wireguard
 
-- name: Install OpenVPN server certificate/key.
+- name: Configure WireGuard&#8482;.
   become: yes
   copy:
-    src: ../Secret/CA/pki/{{ item.path }}.{{ item.typ }}
-    dest: /etc/openvpn/server.{{ item.typ }}
-    mode: <span class="org-string">"{{ item.mode }}"</span>
-  loop:
-  - { path: <span class="org-string">"issued/{{ domain_name }}"</span>, typ: crt,
-      mode: <span class="org-string">"u=r,g=r,o=r"</span> }
-  - { path: <span class="org-string">"private/{{ domain_name }}"</span>, typ: key,
-      mode: <span class="org-string">"u=r,g=,o="</span> }
-  notify: Restart OpenVPN.
-
-- name: Install OpenVPN secrets.
-  become: yes
-  copy:
-    src: ../Secret/{{ item.src }}
-    dest: /etc/openvpn/{{ item.dest }}
+    src: ../private/front-wg0.conf
+    dest: /etc/wireguard/wg0.conf
     <span class="org-variable-name">mode: u</span>=r,g=,o=
-  loop:
-  - { src: front-dh2048.pem, dest: dh2048.pem }
-  - { src: front-shared.key, dest: shared.key }
-  notify: Restart OpenVPN.
-
-- name: Configure OpenVPN.
-  become: yes
-  copy:
-    content: |
-      &lt;&lt;openvpn-front&gt;&gt;
-    dest: /etc/openvpn/server.conf
-    <span class="org-variable-name">mode: u</span>=r,g=r,o=
-  notify: Restart OpenVPN.
+    owner: root
+    group: root
+  notify: Restart WireGuard&#8482;.
 
-- name: Enable/Start OpenVPN.
+- name: Enable/Start WireGuard&#8482; on boot.
   become: yes
   systemd:
-    service: openvpn@server
+    service: wg-quick@wg0
     enabled: yes
     state: started
 </code></pre>
@@ -2751,17 +2566,17 @@ configure the OpenVPN server on Front.
 
 <div class="org-src-container">
 <a href="roles_t/front/handlers/main.yml"><q>roles_t/front/handlers/main.yml</q></a><pre class="src src-conf"><code>
-- name: Restart OpenVPN.
+- name: Restart WireGuard&#8482;.
   become: yes
   systemd:
-    service: openvpn@server
+    service: wg-quick@wg0
     state: restarted
 </code></pre>
 </div>
 </div>
 </div>
-<div id="outline-container-org4ec56d2" class="outline-3">
-<h3 id="org4ec56d2"><span class="section-number-3">7.15.</span> Configure Kamailio</h3>
+<div id="outline-container-org0a1eb35" class="outline-3">
+<h3 id="org0a1eb35"><span class="section-number-3">7.15.</span> Configure Kamailio</h3>
 <div class="outline-text-3" id="text-7-15">
 <p>
 Front uses Kamailio to provide a SIP service on the public VPN so that
@@ -2779,11 +2594,11 @@ the public VPN.  To enforce this expectation, Kamailio is instructed
 to listen <i>only</i> on Front's public VPN.  The private name
 <code>sip.small.private</code> resolves to this address for the convenience
 of members configuring SIP clients.  The server configuration
-specifies the actual IP, known here as <code>front_private_addr</code>.
+specifies the actual IP, known here as <code>front_wg_addr</code>.
 </p>
 
 <div class="org-src-container">
-<code>kamailio</code><pre class="src src-conf" id="orga8e875c"><code><span class="org-variable-name">listen</span>=udp:{{ front_private_addr }}:5060
+<code>kamailio</code><pre class="src src-conf" id="org21ef356"><code><span class="org-variable-name">listen</span>=udp:{{ front_wg_addr }}:5060
 </code></pre>
 </div>
 
@@ -2808,9 +2623,9 @@ The first step is to install Kamailio.
 
 <p>
 Now the configuration drop concerns the network device on which
-Kamailio will be listening, the <code>tun</code> device created by OpenVPN.  The
-added configuration settings inform Systemd that Kamailio should not
-be started before the <code>tun</code> device has appeared.
+Kamailio will be listening, the <code>wg0</code> device created by WireGuard™.
+The added configuration settings inform Systemd that Kamailio should
+not be started before the <code>wg0</code> device has appeared.
 </p>
 
 <div class="org-src-container">
@@ -2821,13 +2636,13 @@ be started before the <code>tun</code> device has appeared.
     path: /etc/systemd/system/kamailio.service.d
     state: directory
 
-- name: Create Kamailio dependence on OpenVPN server.
+- name: Create Kamailio dependence on WireGuard&#8482; interface.
   become: yes
   copy:
     content: |
       [<span class="org-type">Unit</span>]
-      <span class="org-variable-name">Requires</span>=sys-devices-virtual-net-ovpn.device
-      <span class="org-variable-name">After</span>=sys-devices-virtual-net-ovpn.device
+      <span class="org-variable-name">Requires</span>=sys-devices-virtual-net-wg0.device
+      <span class="org-variable-name">After</span>=sys-devices-virtual-net-wg0.device
     dest: /etc/systemd/system/kamailio.service.d/depend.conf
   notify: Reload Systemd.
 </code></pre>
@@ -2877,22 +2692,22 @@ Finally, Kamailio can be configured and started.
 </div>
 </div>
 </div>
-<div id="outline-container-orge6d718e" class="outline-2">
-<h2 id="orge6d718e"><span class="section-number-2">8.</span> The Core Role</h2>
+<div id="outline-container-org03278f3" class="outline-2">
+<h2 id="org03278f3"><span class="section-number-2">8.</span> The Core Role</h2>
 <div class="outline-text-2" id="text-8">
 <p>
 The <code>core</code> role configures many essential campus network services as
 well as the institute's private cloud, so the core machine has
 horsepower (CPUs and RAM) and large disks and is prepared with a
 Debian install and remote access to a privileged, administrator's
-account.  (For details, see <a href="#orgae4ce75">The Core Machine</a>.)
+account.  (For details, see <a href="#org935c0f2">The Core Machine</a>.)
 </p>
 </div>
-<div id="outline-container-org9f10251" class="outline-3">
-<h3 id="org9f10251"><span class="section-number-3">8.1.</span> Include Particulars</h3>
+<div id="outline-container-org6c77b72" class="outline-3">
+<h3 id="org6c77b72"><span class="section-number-3">8.1.</span> Include Particulars</h3>
 <div class="outline-text-3" id="text-8-1">
 <p>
-The first task, as in <a href="#org1024107">The Front Role</a>, is to include the institute
+The first task, as in <a href="#orgd6c5c74">The Front Role</a>, is to include the institute
 particulars and membership roll.
 </p>
 
@@ -2911,8 +2726,8 @@ particulars and membership roll.
 </div>
 </div>
 </div>
-<div id="outline-container-orgf5285f9" class="outline-3">
-<h3 id="orgf5285f9"><span class="section-number-3">8.2.</span> Configure Hostname</h3>
+<div id="outline-container-org3157392" class="outline-3">
+<h3 id="org3157392"><span class="section-number-3">8.2.</span> Configure Hostname</h3>
 <div class="outline-text-3" id="text-8-2">
 <p>
 This task ensures that Core's <q>/etc/hostname</q> and <q>/etc/mailname</q> are
@@ -2945,8 +2760,8 @@ proper email delivery.
 </div>
 </div>
 </div>
-<div id="outline-container-org06a86c3" class="outline-3">
-<h3 id="org06a86c3"><span class="section-number-3">8.3.</span> Configure Systemd Resolved</h3>
+<div id="outline-container-orge0057bb" class="outline-3">
+<h3 id="orge0057bb"><span class="section-number-3">8.3.</span> Configure Systemd Resolved</h3>
 <div class="outline-text-3" id="text-8-3">
 <p>
 Core runs the campus name server, so Resolved is configured to use it
@@ -2990,17 +2805,15 @@ list, and to disable its cache and stub listener.
 </div>
 </div>
 </div>
-<div id="outline-container-org9272d80" class="outline-3">
-<h3 id="org9272d80"><span class="section-number-3">8.4.</span> Configure Netplan</h3>
+<div id="outline-container-orga3b52f5" class="outline-3">
+<h3 id="orga3b52f5"><span class="section-number-3">8.4.</span> Configure Netplan</h3>
 <div class="outline-text-3" id="text-8-4">
 <p>
 Core's network interface is statically configured using Netplan and an
 <q>/etc/netplan/60-core.yaml</q> file.  That file provides Core's address
 on the private Ethernet, the campus name server and search domain, and
 the default route through Gate to the campus ISP.  A second route,
-through Core itself to Front, is advertised to other hosts, but is not
-created here.  It is created by OpenVPN when Core connects to Front's
-VPN.
+through Core itself to Front, is advertised to other hosts.
 </p>
 
 <p>
@@ -3050,8 +2863,8 @@ fact was an empty hash at first boot on a simulated campus Ethernet.)
 </div>
 </div>
 </div>
-<div id="outline-container-orgf1c124e" class="outline-3">
-<h3 id="orgf1c124e"><span class="section-number-3">8.5.</span> Configure DHCP For the Private Ethernet</h3>
+<div id="outline-container-org3efb09c" class="outline-3">
+<h3 id="org3efb09c"><span class="section-number-3">8.5.</span> Configure DHCP For the Private Ethernet</h3>
 <div class="outline-text-3" id="text-8-5">
 <p>
 Core speaks DHCP (Dynamic Host Configuration Protocol) using the
@@ -3091,7 +2904,8 @@ log-facility daemon;
   option broadcast-address 192.168.56.255;
   option routers 192.168.56.2;
   option ntp-servers 192.168.56.1;
-  option rfc3442-routes 24, 10,177,86, 192,168,56,1, 0, 192,168,56,2;
+  option rfc3442-routes 24, 10,177,87, 192,168,56,1,
+                        0,             192,168,56,2;
 }
 
 <span class="org-type">host core</span> {
@@ -3149,12 +2963,12 @@ the real <a href="private/core-dhcpd.conf"><q>private/core-dhcpd.conf</q></a> (<
 </div>
 </div>
 </div>
-<div id="outline-container-org40752b1" class="outline-3">
-<h3 id="org40752b1"><span class="section-number-3">8.6.</span> Configure BIND9</h3>
+<div id="outline-container-org0f63368" class="outline-3">
+<h3 id="org0f63368"><span class="section-number-3">8.6.</span> Configure BIND9</h3>
 <div class="outline-text-3" id="text-8-6">
 <p>
 Core uses BIND9 to provide name service for the institute as described
-in <a href="#org189ccc3">The Name Service</a>.  The configuration supports reverse name lookups,
+in <a href="#org7a5fce0">The Name Service</a>.  The configuration supports reverse name lookups,
 resolving many private network addresses to private domain names.
 </p>
 
@@ -3219,11 +3033,11 @@ probably be used as forwarders rather than Google.
 </p>
 
 <div class="org-src-container">
-<code>bind-options</code><pre class="src src-conf" id="orgaf3e96b"><code><span class="org-type">acl </span><span class="org-string"><span class="org-type">"trusted"</span></span> {
+<code>bind-options</code><pre class="src src-conf" id="org29770de"><code><span class="org-type">acl </span><span class="org-string"><span class="org-type">"trusted"</span></span> {
         {{ private_net_cidr }};
         {{ wild_net_cidr }};
-        {{ public_vpn_net_cidr }};
-        {{ campus_vpn_net_cidr }};
+        {{ public_wg_net_cidr }};
+        {{ campus_wg_net_cidr }};
         localhost;
 };
 
@@ -3248,7 +3062,7 @@ probably be used as forwarders rather than Google.
 </div>
 
 <div class="org-src-container">
-<code>bind-local</code><pre class="src src-conf" id="org5ab6230"><code>include <span class="org-string">"/etc/bind/zones.rfc1918"</span>;
+<code>bind-local</code><pre class="src src-conf" id="orgf98ba3a"><code>include <span class="org-string">"/etc/bind/zones.rfc1918"</span>;
 
 <span class="org-type">zone </span><span class="org-string"><span class="org-type">"{{ domain_priv }}."</span></span> {
         type master;
@@ -3261,13 +3075,13 @@ probably be used as forwarders rather than Google.
         file <span class="org-string">"/etc/bind/db.private"</span>;
 };
 
-<span class="org-type">zone </span><span class="org-string"><span class="org-type">"{</span></span><span class="org-string">{ public_vpn_net_cidr | ansible.utils.ipaddr('revdns')</span>
+<span class="org-type">zone </span><span class="org-string"><span class="org-type">"{</span></span><span class="org-string">{ public_wg_net_cidr | ansible.utils.ipaddr('revdns')</span>
 <span class="org-string">         </span><span class="org-string"><span class="org-type">| regex_replace('^0\.','') }}"</span></span> {
         type master;
         file <span class="org-string">"/etc/bind/db.public_vpn"</span>;
 };
 
-<span class="org-type">zone </span><span class="org-string"><span class="org-type">"{</span></span><span class="org-string">{ campus_vpn_net_cidr | ansible.utils.ipaddr('revdns')</span>
+<span class="org-type">zone </span><span class="org-string"><span class="org-type">"{</span></span><span class="org-string">{ campus_wg_net_cidr | ansible.utils.ipaddr('revdns')</span>
 <span class="org-string">         </span><span class="org-string"><span class="org-type">| regex_replace('^0\.','') }}"</span></span> {
         type master;
         file <span class="org-string">"/etc/bind/db.campus_vpn"</span>;
@@ -3296,7 +3110,7 @@ probably be used as forwarders rather than Google.
 <span class="org-string">test    IN      CNAME   core.small.private.</span>
 <span class="org-string">live    IN      CNAME   core.small.private.</span>
 <span class="org-string">ntp     IN      CNAME   core.small.private.</span>
-<span class="org-string">sip     IN      A       10.177.86.1</span>
+<span class="org-string">sip     IN      A       10.177.87.1</span>
 <span class="org-string">;</span>
 <span class="org-string">core    IN      A       192.168.56.1</span>
 <span class="org-string">gate    IN      A       192.168.56.2</span>
@@ -3360,8 +3174,8 @@ probably be used as forwarders rather than Google.
 </div>
 </div>
 </div>
-<div id="outline-container-org7b737c7" class="outline-3">
-<h3 id="org7b737c7"><span class="section-number-3">8.7.</span> Add Administrator to System Groups</h3>
+<div id="outline-container-org72f1783" class="outline-3">
+<h3 id="org72f1783"><span class="section-number-3">8.7.</span> Add Administrator to System Groups</h3>
 <div class="outline-text-3" id="text-8-7">
 <p>
 The administrator often needs to read (directories of) log files owned
@@ -3381,15 +3195,15 @@ these groups speeds up debugging.
 </div>
 </div>
 </div>
-<div id="outline-container-orgd39c2c0" class="outline-3">
-<h3 id="orgd39c2c0"><span class="section-number-3">8.8.</span> Configure Monkey</h3>
+<div id="outline-container-org97f4e6a" class="outline-3">
+<h3 id="org97f4e6a"><span class="section-number-3">8.8.</span> Configure Monkey</h3>
 <div class="outline-text-3" id="text-8-8">
 <p>
 The small institute runs cron jobs and web scripts that generate
 reports and perform checks.  The un-privileged jobs are run by a
 system account named <code>monkey</code>.  One of Monkey's more important jobs on
 Core is to run <code>rsync</code> to update the public web site on Front (as
-described in <a href="#org682dcfc">*Configure Apache2</a>).
+described in <a href="#orgd35be7c">*Configure Apache2</a>).
 </p>
 
 <div class="org-src-container">
@@ -3449,8 +3263,8 @@ described in <a href="#org682dcfc">*Configure Apache2</a>).
 </div>
 </div>
 </div>
-<div id="outline-container-orgb3bf17a" class="outline-3">
-<h3 id="orgb3bf17a"><span class="section-number-3">8.9.</span> Install Unattended Upgrades</h3>
+<div id="outline-container-orgd22f668" class="outline-3">
+<h3 id="orgd22f668"><span class="section-number-3">8.9.</span> Install Unattended Upgrades</h3>
 <div class="outline-text-3" id="text-8-9">
 <p>
 The institute prefers to install security updates as soon as possible.
@@ -3465,11 +3279,11 @@ The institute prefers to install security updates as soon as possible.
 </div>
 </div>
 </div>
-<div id="outline-container-org9e31a35" class="outline-3">
-<h3 id="org9e31a35"><span class="section-number-3">8.10.</span> Install Expect</h3>
+<div id="outline-container-orgd7b5013" class="outline-3">
+<h3 id="orgd7b5013"><span class="section-number-3">8.10.</span> Install Expect</h3>
 <div class="outline-text-3" id="text-8-10">
 <p>
-The <code>expect</code> program is used by <a href="#org5db480b">The Institute Commands</a> to interact
+The <code>expect</code> program is used by <a href="#org1eb4766">The Institute Commands</a> to interact
 with Nextcloud on the command line.
 </p>
 
@@ -3482,12 +3296,12 @@ with Nextcloud on the command line.
 </div>
 </div>
 </div>
-<div id="outline-container-org2d28846" class="outline-3">
-<h3 id="org2d28846"><span class="section-number-3">8.11.</span> Configure User Accounts</h3>
+<div id="outline-container-org0770c20" class="outline-3">
+<h3 id="org0770c20"><span class="section-number-3">8.11.</span> Configure User Accounts</h3>
 <div class="outline-text-3" id="text-8-11">
 <p>
 User accounts are created immediately so that backups can begin
-restoring as soon as possible.  The <a href="#org6138e49">Account Management</a> chapter
+restoring as soon as possible.  The <a href="#org944d8f7">Account Management</a> chapter
 describes the <code>members</code> and <code>usernames</code> variables.
 </p>
 
@@ -3525,8 +3339,8 @@ describes the <code>members</code> and <code>usernames</code> variables.
 </div>
 </div>
 </div>
-<div id="outline-container-org3c35b95" class="outline-3">
-<h3 id="org3c35b95"><span class="section-number-3">8.12.</span> Install Server Certificate</h3>
+<div id="outline-container-orgb8f0771" class="outline-3">
+<h3 id="orgb8f0771"><span class="section-number-3">8.12.</span> Install Server Certificate</h3>
 <div class="outline-text-3" id="text-8-12">
 <p>
 The servers on Core use the same certificate (and key) to authenticate
@@ -3550,13 +3364,12 @@ themselves to institute clients.  They share the <q>/etc/server.crt</q> and
   notify:
   - Restart Postfix.
   - Restart Dovecot.
-  - Restart OpenVPN.
 </code></pre>
 </div>
 </div>
 </div>
-<div id="outline-container-org668ece0" class="outline-3">
-<h3 id="org668ece0"><span class="section-number-3">8.13.</span> Install NTP</h3>
+<div id="outline-container-org19f1511" class="outline-3">
+<h3 id="org19f1511"><span class="section-number-3">8.13.</span> Install NTP</h3>
 <div class="outline-text-3" id="text-8-13">
 <p>
 Core uses NTP to provide a time synchronization service to the campus.
@@ -3572,8 +3385,8 @@ The default daemon's default configuration is fine.
 </div>
 </div>
 </div>
-<div id="outline-container-org296b053" class="outline-3">
-<h3 id="org296b053"><span class="section-number-3">8.14.</span> Configure Postfix on Core</h3>
+<div id="outline-container-org67c82ee" class="outline-3">
+<h3 id="org67c82ee"><span class="section-number-3">8.14.</span> Configure Postfix on Core</h3>
 <div class="outline-text-3" id="text-8-14">
 <p>
 Core uses Postfix to provide SMTP service to the campus.  The default
@@ -3589,7 +3402,7 @@ The appropriate answers are listed here but will be checked
 </ul>
 
 <p>
-As discussed in <a href="#org73635f2">The Email Service</a> above, Core delivers email addressed
+As discussed in <a href="#org474e9f9">The Email Service</a> above, Core delivers email addressed
 to any internal domain name locally, and uses its smarthost Front to
 relay the rest.  Core is reachable only on institute networks, so
 there is little benefit in enabling TLS, but it does need to handle
@@ -3602,11 +3415,11 @@ Core relays messages from any institute network.
 </p>
 
 <div class="org-src-container">
-<code>postfix-core-networks</code><pre class="src src-conf" id="org24ddb59"><code>- p: mynetworks
+<code>postfix-core-networks</code><pre class="src src-conf" id="org52d047a"><code>- p: mynetworks
   v: &gt;-
      {{ private_net_cidr }}
-     {{ public_vpn_net_cidr }}
-     {{ campus_vpn_net_cidr }}
+     {{ public_wg_net_cidr }}
+     {{ campus_wg_net_cidr }}
      127.0.0.0/8
      [<span class="org-type">::ffff:127.0.0.0</span>]/104
      [<span class="org-type">::1</span>]/128
@@ -3618,7 +3431,7 @@ Core uses Front to relay messages to the Internet.
 </p>
 
 <div class="org-src-container">
-<code>postfix-core-relayhost</code><pre class="src src-conf" id="orgabfee7f"><code>- { p: relayhost, v: <span class="org-string">"[{{ front_private_addr }}]"</span> }
+<code>postfix-core-relayhost</code><pre class="src src-conf" id="org73301ab"><code>- { p: relayhost, v: <span class="org-string">"[{{ front_wg_addr }}]"</span> }
 </code></pre>
 </div>
 
@@ -3630,7 +3443,7 @@ file.
 </p>
 
 <div class="org-src-container">
-<code>postfix-transport</code><pre class="src src-conf" id="org7dc82f6"><code>.{{ domain_name }}      local:$myhostname
+<code>postfix-transport</code><pre class="src src-conf" id="org4a27214"><code>.{{ domain_name }}      local:$myhostname
 .{{ domain_priv }}      local:$myhostname
 </code></pre>
 </div>
@@ -3641,7 +3454,7 @@ The complete list of Core's Postfix settings for
 </p>
 
 <div class="org-src-container">
-<code>postfix-core</code><pre class="src src-conf" id="org7e631cc"><code>&lt;&lt;postfix-relaying&gt;&gt;
+<code>postfix-core</code><pre class="src src-conf" id="org7c21d40"><code>&lt;&lt;postfix-relaying&gt;&gt;
 - { p: smtpd_tls_security_level, v: none }
 - { p: smtp_tls_security_level, v: none }
 &lt;&lt;postfix-message-size&gt;&gt;
@@ -3712,12 +3525,12 @@ enable the service.  Whenever <q>/etc/postfix/transport</q> is changed, the
 </div>
 </div>
 </div>
-<div id="outline-container-org8d9476a" class="outline-3">
-<h3 id="org8d9476a"><span class="section-number-3">8.15.</span> Configure Private Email Aliases</h3>
+<div id="outline-container-org66d1a08" class="outline-3">
+<h3 id="org66d1a08"><span class="section-number-3">8.15.</span> Configure Private Email Aliases</h3>
 <div class="outline-text-3" id="text-8-15">
 <p>
 The institute's Core needs to deliver email addressed to institute
-aliases including those advertised on the campus web site, in VPN
+aliases including those advertised on the campus web site, in X.509
 certificates, etc.  System daemons like <code>cron(8)</code> may also send email
 to e.g. <code>monkey</code>.  The following aliases are installed in
 <q>/etc/aliases</q> with a special marker so that additional blocks can be
@@ -3750,8 +3563,8 @@ installed by more specialized roles.
 </div>
 </div>
 </div>
-<div id="outline-container-org77e47c0" class="outline-3">
-<h3 id="org77e47c0"><span class="section-number-3">8.16.</span> Configure Dovecot IMAPd</h3>
+<div id="outline-container-orgfbad8af" class="outline-3">
+<h3 id="orgfbad8af"><span class="section-number-3">8.16.</span> Configure Dovecot IMAPd</h3>
 <div class="outline-text-3" id="text-8-16">
 <p>
 Core uses Dovecot's IMAPd to store and serve member emails.  As on
@@ -3761,7 +3574,7 @@ top" given that Core is only accessed from private (encrypted)
 networks, but helps to ensure privacy even when members accidentally
 attempt connections from outside the private networks.  For more
 information about Core's role in the institute's email services, see
-<a href="#org73635f2">The Email Service</a>.
+<a href="#org474e9f9">The Email Service</a>.
 </p>
 
 <p>
@@ -3769,7 +3582,7 @@ The institute follows the recommendation in the package
 <q>README.Debian</q> (in <q>/usr/share/dovecot-core/</q>) but replaces the
 default "snake oil" certificate with another, signed by the institute.
 (For more information about the institute's X.509 certificates, see
-<a href="#org1c1be46">Keys</a>.)
+<a href="#org8244014">Keys</a>.)
 </p>
 
 <p>
@@ -3815,8 +3628,8 @@ and enables it to start at every reboot.
 </div>
 </div>
 </div>
-<div id="outline-container-orgea0cf88" class="outline-3">
-<h3 id="orgea0cf88"><span class="section-number-3">8.17.</span> Configure Fetchmail</h3>
+<div id="outline-container-orge7d5910" class="outline-3">
+<h3 id="orge7d5910"><span class="section-number-3">8.17.</span> Configure Fetchmail</h3>
 <div class="outline-text-3" id="text-8-17">
 <p>
 Core runs a <code>fetchmail</code> for each member of the institute.  Individual
@@ -3833,14 +3646,14 @@ the username.  The template is only used when the record has a
 </p>
 
 <div class="org-src-container">
-<code>fetchmail-config</code><pre class="src src-conf" id="orge18ab2e"><code><span class="org-comment-delimiter"># </span><span class="org-comment">Permissions on this file may be no greater than 0600.</span>
+<code>fetchmail-config</code><pre class="src src-conf" id="org486cf27"><code><span class="org-comment-delimiter"># </span><span class="org-comment">Permissions on this file may be no greater than 0600.</span>
 
 set no bouncemail
 set no spambounce
 set no syslog
 <span class="org-comment-delimiter">#</span><span class="org-comment">set logfile /home/{{ item }}/.fetchmail.log</span>
 
-poll {{ front_private_addr }} protocol imap timeout 15
+poll {{ front_wg_addr }} protocol imap timeout 15
     username {{ item }}
     password <span class="org-string">"{{ members[item].password_fetchmail }}"</span> fetchall
     ssl sslproto tls1.2+ sslcertck sslcommonname {{ domain_name }}
@@ -3852,11 +3665,11 @@ The Systemd service description.
 </p>
 
 <div class="org-src-container">
-<code>fetchmail-service</code><pre class="src src-conf" id="orga4027c9"><code>[<span class="org-type">Unit</span>]
+<code>fetchmail-service</code><pre class="src src-conf" id="org7df6879"><code>[<span class="org-type">Unit</span>]
 <span class="org-variable-name">Description</span>=Fetchmail --idle task for {{ item }}.
 <span class="org-variable-name">AssertPathExists</span>=/home/{{ item }}/.fetchmailrc
-<span class="org-variable-name">After</span>=openvpn@front.service
-<span class="org-variable-name">Wants</span>=sys-devices-virtual-net-ovpn.device
+<span class="org-variable-name">After</span>=wg-quick@wg0.service
+<span class="org-variable-name">Wants</span>=sys-devices-virtual-net-wg0.device
 
 [<span class="org-type">Service</span>]
 <span class="org-variable-name">User</span>={{ item }}
@@ -3969,12 +3782,12 @@ Otherwise the following task might be appropriate.
 </div>
 </div>
 </div>
-<div id="outline-container-orgbdf8137" class="outline-3">
-<h3 id="orgbdf8137"><span class="section-number-3">8.18.</span> Configure Apache2 <a id="org682dcfc"></a></h3>
+<div id="outline-container-org51c6dbc" class="outline-3">
+<h3 id="org51c6dbc"><span class="section-number-3">8.18.</span> Configure Apache2 <a id="orgd35be7c"></a></h3>
 <div class="outline-text-3" id="text-8-18">
 <p>
 This is the small institute's campus web server.  It hosts several web
-sites as described in <a href="#orgf659a90">The Web Services</a>.
+sites as described in <a href="#org4e9f4f9">The Web Services</a>.
 </p>
 
 <table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
@@ -4045,7 +3858,7 @@ naming a sub-directory in the member's home directory on Core.  The
 </p>
 
 <div class="org-src-container">
-<code>apache-userdir-core</code><pre class="src src-conf" id="orge344fe6"><code>UserDir Public/HTML
+<code>apache-userdir-core</code><pre class="src src-conf" id="orgc8c0ba8"><code>UserDir Public/HTML
 &lt;Directory /home/*/Public/HTML/&gt;
         Require all granted
         AllowOverride None
@@ -4060,7 +3873,7 @@ redirect, the encryption ciphers and certificates.
 </p>
 
 <div class="org-src-container">
-<code>apache-live</code><pre class="src src-conf" id="org6306943"><code>&lt;VirtualHost *:80&gt;
+<code>apache-live</code><pre class="src src-conf" id="org0dc13cb"><code>&lt;VirtualHost *:80&gt;
         ServerName live
         ServerAlias live.{{ domain_priv }}
         ServerAdmin webmaster@core.{{ domain_priv }}
@@ -4087,7 +3900,7 @@ familiar.
 </p>
 
 <div class="org-src-container">
-<code>apache-test</code><pre class="src src-conf" id="org455bb19"><code>&lt;VirtualHost *:80&gt;
+<code>apache-test</code><pre class="src src-conf" id="org0b1b4a5"><code>&lt;VirtualHost *:80&gt;
         ServerName test
         ServerAlias test.{{ domain_priv }}
         ServerAdmin webmaster@core.{{ domain_priv }}
@@ -4116,7 +3929,7 @@ trained staffers, monitored by a revision control system, etc.
 </p>
 
 <div class="org-src-container">
-<code>apache-campus</code><pre class="src src-conf" id="org474a92e"><code>&lt;VirtualHost *:80&gt;
+<code>apache-campus</code><pre class="src src-conf" id="org96d6bd5"><code>&lt;VirtualHost *:80&gt;
         ServerName www
         ServerAlias www.{{ domain_priv }}
         ServerAdmin webmaster@core.{{ domain_priv }}
@@ -4233,8 +4046,8 @@ The <code>a2ensite</code> command enables them.
 </div>
 </div>
 </div>
-<div id="outline-container-org88cbb1f" class="outline-3">
-<h3 id="org88cbb1f"><span class="section-number-3">8.19.</span> Configure Website Updates</h3>
+<div id="outline-container-org97cebd9" class="outline-3">
+<h3 id="org97cebd9"><span class="section-number-3">8.19.</span> Configure Website Updates</h3>
 <div class="outline-text-3" id="text-8-19">
 <p>
 Monkey on Core runs <q>/usr/local/sbin/webupdate</q> every 15 minutes via a
@@ -4243,7 +4056,7 @@ Monkey on Core runs <q>/usr/local/sbin/webupdate</q> every 15 minutes via a
 </p>
 
 <div class="org-src-container">
-<a href="private/webupdate"><q>private/webupdate</q></a><pre class="src src-sh" id="org56430fc"><code><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span><span class="org-comment"> -e</span>
+<a href="private/webupdate"><q>private/webupdate</q></a><pre class="src src-sh" id="orgc32ac69"><code><span class="org-comment-delimiter">#</span><span class="org-comment">!/bin/</span><span class="org-keyword">bash</span><span class="org-comment"> -e</span>
 <span class="org-comment-delimiter">#</span>
 <span class="org-comment-delimiter"># </span><span class="org-comment">DO NOT EDIT.  This file was tangled from institute.org.</span>
 
@@ -4259,7 +4072,7 @@ rsync -avz --delete --chmod=g-w         <span class="org-sh-escaped-newline">\</
 <p>
 The following tasks install the <q>webupdate</q> script from <a href="private/"><q>private/</q></a>,
 and create Monkey's <code>cron</code> job.  An example <q>webupdate</q> script is
-provided <a href="#org56430fc">here</a>.
+provided <a href="#orgc32ac69">here</a>.
 </p>
 
 <div class="org-src-container">
@@ -4284,109 +4097,79 @@ provided <a href="#org56430fc">here</a>.
 </div>
 </div>
 </div>
-<div id="outline-container-org31f8027" class="outline-3">
-<h3 id="org31f8027"><span class="section-number-3">8.20.</span> Configure OpenVPN Connection to Front</h3>
+<div id="outline-container-orgd2fde7b" class="outline-3">
+<h3 id="orgd2fde7b"><span class="section-number-3">8.20.</span> Configure Core WireGuard™ Interface</h3>
 <div class="outline-text-3" id="text-8-20">
 <p>
-Core connects to Front's public VPN to provide members abroad with a
-route to the campus networks.  As described in the configuration of
-Front's OpenVPN service, Front expects Core to connect using a client
-certificate with Common Name <code>Core</code>.
+Core connects to Front's WireGuard™ service to provide members abroad
+with a route to the campus networks.  As described in <a href="#org290050a">Configure Public
+WireGuard™ Subnet</a> for Front, Core is expected to forward packets from/to the
+private networks.
 </p>
 
 <p>
-Core's OpenVPN client configuration uses the Debian default Systemd
-service unit to keep Core connected to Front.  The configuration
-is installed in <q>/etc/openvpn/front.conf</q> so the Systemd service is
-called <code>openvpn@front</code>.
+The following example <a href="private/core-wg0.conf"><q>private/core-wg0.conf</q></a> configuration recognizes
+Front by its public key, <code>S+6HaT</code>, looking for it at the institute's
+public IP address and a special port.
 </p>
 
 <div class="org-src-container">
-<code>openvpn-core</code><pre class="src src-conf" id="org7db87cf"><code>client
-dev-type tun
-dev ovpn
-remote {{ front_addr }}
-nobind
-&lt;&lt;openvpn-drop-priv&gt;&gt;
-&lt;&lt;openvpn-crypt&gt;&gt;
-remote-cert-tls server
-verify-x509-name {{ domain_name }} name
-verb 3
-ca /usr/local/share/ca-certificates/{{ domain_name }}.crt
-cert client.crt
-key client.key
-tls-crypt shared.key
+<a href="private/core-wg0.conf"><q>private/core-wg0.conf</q></a><pre class="src src-conf"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.177.87.2
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Front</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">EndPoint</span> = 192.168.15.5:39608
+<span class="org-variable-name">PublicKey</span> = S+6HaTnOwwhWgUGXjSBcPAvifKw+j8BDTRfq534gNW4=
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.1
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.0/24
 </code></pre>
 </div>
 
 <p>
-The tasks that install and configure the OpenVPN client configuration
-for Core.
+The following tasks install WireGuard™, configure it with
+<a href="private/core-wg0.conf"><q>private/core-wg0.conf</q></a>, and enable the service.
 </p>
 
 <div class="org-src-container">
 <a href="roles_t/core/tasks/main.yml"><q>roles_t/core/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Install OpenVPN.
-  become: yes
-  <span class="org-variable-name">apt: pkg</span>=openvpn
-
-- name: Enable IP forwarding.
+- name: Install WireGuard&#8482;.
   become: yes
-  sysctl:
-    name: net.ipv4.ip_forward
-    value: <span class="org-string">"1"</span>
-    state: present
+  <span class="org-variable-name">apt: pkg</span>=wireguard
 
-- name: Install OpenVPN secret.
+- name: Configure WireGuard&#8482;.
   become: yes
   copy:
-    src: ../Secret/front-shared.key
-    dest: /etc/openvpn/shared.key
+    src: ../private/core-wg0.conf
+    dest: /etc/wireguard/wg0.conf
     <span class="org-variable-name">mode: u</span>=r,g=,o=
-  notify: Restart OpenVPN.
-
-- name: Install OpenVPN client certificate/key.
-  become: yes
-  copy:
-    src: ../Secret/CA/pki/{{ item.path }}.{{ item.typ }}
-    dest: /etc/openvpn/client.{{ item.typ }}
-    mode: <span class="org-string">"{{ item.mode }}"</span>
-  loop:
-  - { path: <span class="org-string">"issued/core"</span>, typ: crt, mode: <span class="org-string">"u=r,g=r,o=r"</span> }
-  - { path: <span class="org-string">"private/core"</span>, typ: key, mode: <span class="org-string">"u=r,g=,o="</span> }
-  notify: Restart OpenVPN.
-
-- name: Configure OpenVPN.
-  become: yes
-  copy:
-    content: |
-      &lt;&lt;openvpn-core&gt;&gt;
-    dest: /etc/openvpn/front.conf
-    <span class="org-variable-name">mode: u</span>=r,g=r,o=
-  notify: Restart OpenVPN.
+    owner: root
+    group: root
+  notify: Restart WireGuard&#8482;.
 
-- name: Enable/Start OpenVPN.
+- name: Enable/Start WireGuard&#8482; on boot.
   become: yes
   systemd:
-    service: openvpn@front
-    state: started
+    service: wg-quick@wg0
     enabled: yes
+    state: started
 </code></pre>
 </div>
 
 <div class="org-src-container">
 <a href="roles_t/core/handlers/main.yml"><q>roles_t/core/handlers/main.yml</q></a><pre class="src src-conf"><code>
-- name: Restart OpenVPN.
+- name: Restart WireGuard&#8482;.
   become: yes
   systemd:
-    service: openvpn@front
+    service: wg-quick@wg0
     state: restarted
 </code></pre>
 </div>
 </div>
 </div>
-<div id="outline-container-org6cb627e" class="outline-3">
-<h3 id="org6cb627e"><span class="section-number-3">8.21.</span> Configure NAGIOS</h3>
+<div id="outline-container-org7a1b1ea" class="outline-3">
+<h3 id="org7a1b1ea"><span class="section-number-3">8.21.</span> Configure NAGIOS</h3>
 <div class="outline-text-3" id="text-8-21">
 <p>
 Core runs a <code>nagios4</code> server to monitor "services" on institute hosts.
@@ -4429,7 +4212,8 @@ Core and Campus (and thus Gate) machines.
     backrefs: yes
   loop:
   - { regexp: <span class="org-string">"^( *cfg_file *= *localhost.cfg)"</span>, line: <span class="org-string">"# \\1"</span> }
-  - { regexp: <span class="org-string">"^( *admin_email *= *)"</span>, line: <span class="org-string">"\\1{{ ansible_user }}@localhost"</span> }
+  <span class="org-type">-</span> { regexp: <span class="org-string">"^( *admin_email *= *)"</span>,
+      line: <span class="org-string">"\\1{{ ansible_user }}@localhost"</span> }
   notify: Reload NAGIOS4.
 
 - name: Configure NAGIOS4 contacts.
@@ -4467,8 +4251,8 @@ Core and Campus (and thus Gate) machines.
 </code></pre>
 </div>
 </div>
-<div id="outline-container-org74a0c8d" class="outline-4">
-<h4 id="org74a0c8d"><span class="section-number-4">8.21.1.</span> Configure NAGIOS Monitors for Core</h4>
+<div id="outline-container-org0d76538" class="outline-4">
+<h4 id="org0d76538"><span class="section-number-4">8.21.1.</span> Configure NAGIOS Monitors for Core</h4>
 <div class="outline-text-4" id="text-8-21-1">
 <p>
 The first block in <q>nagios.cfg</q> specifies monitors for services on
@@ -4543,8 +4327,8 @@ used here <i>may</i> specify plugin arguments.
 </div>
 </div>
 </div>
-<div id="outline-container-orgb9ed6e7" class="outline-4">
-<h4 id="orgb9ed6e7"><span class="section-number-4">8.21.2.</span> Custom NAGIOS Monitor <code>inst_sensors</code></h4>
+<div id="outline-container-orgcdd5318" class="outline-4">
+<h4 id="orgcdd5318"><span class="section-number-4">8.21.2.</span> Custom NAGIOS Monitor <code>inst_sensors</code></h4>
 <div class="outline-text-4" id="text-8-21-2">
 <p>
 The <code>check_sensors</code> plugin is included in the package
@@ -4572,7 +4356,8 @@ small institute substitutes a slightly modified version,
         <span class="org-builtin">echo</span> <span class="org-string">""</span>
         print_usage
         <span class="org-builtin">echo</span> <span class="org-string">""</span>
-        <span class="org-builtin">echo</span> <span class="org-string">"This plugin checks hardware status using the lm_sensors package."</span>
+        <span class="org-builtin">echo</span> -n <span class="org-string">"This plugin checks hardware status"</span>
+        <span class="org-builtin">echo</span> <span class="org-string">" using the lm_sensors package."</span>
         <span class="org-builtin">echo</span> <span class="org-string">""</span>
         support
         <span class="org-keyword">exit</span> $<span class="org-variable-name">STATE_OK</span>
@@ -4655,8 +4440,8 @@ Core.
 </div>
 </div>
 </div>
-<div id="outline-container-org4c8f989" class="outline-4">
-<h4 id="org4c8f989"><span class="section-number-4">8.21.3.</span> Configure NAGIOS Monitors for Remote Hosts</h4>
+<div id="outline-container-org2d1e468" class="outline-4">
+<h4 id="org2d1e468"><span class="section-number-4">8.21.3.</span> Configure NAGIOS Monitors for Remote Hosts</h4>
 <div class="outline-text-4" id="text-8-21-3">
 <p>
 The following sections contain code blocks specifying monitors for
@@ -4673,12 +4458,12 @@ plugin with pre-defined arguments appropriate for the institute.  The
 commands are defined in code blocks interleaved with the blocks that
 monitor them.  The command blocks are appended to <q>nrpe.cfg</q> and the
 monitoring blocks to <q>nagios.cfg</q>.  The <q>nrpe.cfg</q> file is installed
-on each campus host by the campus role's <a href="#org356ce5d">Configure NRPE</a> tasks.
+on each campus host by the campus role's <a href="#orgc703c4f">Configure NRPE</a> tasks.
 </p>
 </div>
 </div>
-<div id="outline-container-org5f33bb4" class="outline-4">
-<h4 id="org5f33bb4"><span class="section-number-4">8.21.4.</span> Configure NAGIOS Monitors for Gate</h4>
+<div id="outline-container-org68c6c7d" class="outline-4">
+<h4 id="org68c6c7d"><span class="section-number-4">8.21.4.</span> Configure NAGIOS Monitors for Gate</h4>
 <div class="outline-text-4" id="text-8-21-4">
 <p>
 Define the monitored host, <code>gate</code>.  Monitor its response to network
@@ -4809,12 +4594,12 @@ Monitor <code>inst_sensors</code> on Gate.
 </div>
 </div>
 </div>
-<div id="outline-container-org2323be4" class="outline-3">
-<h3 id="org2323be4"><span class="section-number-3">8.22.</span> Configure Backups</h3>
+<div id="outline-container-org89c82e3" class="outline-3">
+<h3 id="org89c82e3"><span class="section-number-3">8.22.</span> Configure Backups</h3>
 <div class="outline-text-3" id="text-8-22">
 <p>
 The following task installs the <q>backup</q> script from <a href="private/"><q>private/</q></a>.  An
-example script is provided in <a href="#org8d148e8">here</a>.
+example script is provided in <a href="#orgf388eed">here</a>.
 </p>
 
 <div class="org-src-container">
@@ -4829,20 +4614,20 @@ example script is provided in <a href="#org8d148e8">here</a>.
 </div>
 </div>
 </div>
-<div id="outline-container-org640e4f7" class="outline-3">
-<h3 id="org640e4f7"><span class="section-number-3">8.23.</span> Configure Nextcloud</h3>
+<div id="outline-container-org68d4766" class="outline-3">
+<h3 id="org68d4766"><span class="section-number-3">8.23.</span> Configure Nextcloud</h3>
 <div class="outline-text-3" id="text-8-23">
 <p>
 Core runs Nextcloud to provide a private institute cloud, as described
-in <a href="#orgd0834d2">The Cloud Service</a>.  Installing, restoring (from backup), and
+in <a href="#org582454b">The Cloud Service</a>.  Installing, restoring (from backup), and
 upgrading Nextcloud are manual processes documented in <a href="https://docs.nextcloud.com/server/latest/admin_manual/maintenance/">The Nextcloud
 Admin Manual, Maintenance</a>.  However Ansible can help prepare Core
 before an install or restore, and perform basic security checks
 afterwards.
 </p>
 </div>
-<div id="outline-container-orgea15814" class="outline-4">
-<h4 id="orgea15814"><span class="section-number-4">8.23.1.</span> Prepare Core For Nextcloud</h4>
+<div id="outline-container-org2ce637f" class="outline-4">
+<h4 id="org2ce637f"><span class="section-number-4">8.23.1.</span> Prepare Core For Nextcloud</h4>
 <div class="outline-text-4" id="text-8-23-1">
 <p>
 The Ansible code contained herein prepares Core to run Nextcloud by
@@ -5036,7 +4821,7 @@ created manually.
 The following task would work (<code>mysql_user</code> supports
 <code>check_implicit_admin</code>) <i>but</i> the <code>nextcloud</code> database was not created
 above.  Thus both database and user are created manually, with SQL
-given in the <a href="#orgb7a7660">8.23.5</a> subsection below, before <code>occ
+given in the <a href="#org50fbfc3">8.23.5</a> subsection below, before <code>occ
 maintenance:install</code> can run.
 </p>
 
@@ -5074,8 +4859,8 @@ its document root.
 </div>
 </div>
 </div>
-<div id="outline-container-org3f0d821" class="outline-4">
-<h4 id="org3f0d821"><span class="section-number-4">8.23.2.</span> Configure PHP</h4>
+<div id="outline-container-org29e69fb" class="outline-4">
+<h4 id="org29e69fb"><span class="section-number-4">8.23.2.</span> Configure PHP</h4>
 <div class="outline-text-4" id="text-8-23-2">
 <p>
 The following tasks set a number of PHP parameters for better
@@ -5118,8 +4903,8 @@ performance, as recommended by Nextcloud.
 </div>
 </div>
 </div>
-<div id="outline-container-orgdb3439f" class="outline-4">
-<h4 id="orgdb3439f"><span class="section-number-4">8.23.3.</span> Create <q>/Nextcloud/</q></h4>
+<div id="outline-container-org7ba56a3" class="outline-4">
+<h4 id="org7ba56a3"><span class="section-number-4">8.23.3.</span> Create <q>/Nextcloud/</q></h4>
 <div class="outline-text-4" id="text-8-23-3">
 <p>
 The Ansible tasks up to this point have completed Core's LAMP stack
@@ -5177,8 +4962,8 @@ sudo mount /Nextcloud
 </div>
 </div>
 </div>
-<div id="outline-container-org3c8ed7b" class="outline-4">
-<h4 id="org3c8ed7b"><span class="section-number-4">8.23.4.</span> Restore Nextcloud</h4>
+<div id="outline-container-orgabfc848" class="outline-4">
+<h4 id="orgabfc848"><span class="section-number-4">8.23.4.</span> Restore Nextcloud</h4>
 <div class="outline-text-4" id="text-8-23-4">
 <p>
 Restoring Nextcloud in the newly created <q>/Nextcloud/</q> presumably
@@ -5211,7 +4996,7 @@ The database is restored with the following commands, which assume the
 last dump was made February 20th 2022 and thus was saved in
 <q>/Nextcloud/20220220.bak</q>.  The database will need to be
 created first as when installing Nextcloud.  The appropriate SQL are
-given in <a href="#orgb7a7660">Install Nextcloud</a> below.
+given in <a href="#org50fbfc3">Install Nextcloud</a> below.
 </p>
 
 <div class="org-src-container">
@@ -5229,8 +5014,8 @@ Overview web page.
 </p>
 </div>
 </div>
-<div id="outline-container-orgb7a7660" class="outline-4">
-<h4 id="orgb7a7660"><span class="section-number-4">8.23.5.</span> Install Nextcloud</h4>
+<div id="outline-container-org50fbfc3" class="outline-4">
+<h4 id="org50fbfc3"><span class="section-number-4">8.23.5.</span> Install Nextcloud</h4>
 <div class="outline-text-4" id="text-8-23-5">
 <p>
 Installing Nextcloud in the newly created <q>/Nextcloud/</q> starts with
@@ -5300,8 +5085,8 @@ Administration &gt; Overview page.
 </p>
 </div>
 </div>
-<div id="outline-container-orgdc9a64a" class="outline-4">
-<h4 id="orgdc9a64a"><span class="section-number-4">8.23.6.</span> Afterwards</h4>
+<div id="outline-container-orgb106f73" class="outline-4">
+<h4 id="orgb106f73"><span class="section-number-4">8.23.6.</span> Afterwards</h4>
 <div class="outline-text-4" id="text-8-23-6">
 <p>
 Whether Nextcloud was restored or installed, there are a few things
@@ -5483,14 +5268,14 @@ run before the next backup.
 </div>
 </div>
 </div>
-<div id="outline-container-org936487d" class="outline-2">
-<h2 id="org936487d"><span class="section-number-2">9.</span> The Gate Role</h2>
+<div id="outline-container-orgbf7ae97" class="outline-2">
+<h2 id="orgbf7ae97"><span class="section-number-2">9.</span> The Gate Role</h2>
 <div class="outline-text-2" id="text-9">
 <p>
 The <code>gate</code> role configures the services expected at the campus gate:
 access to the private Ethernet from the untrusted Ethernet (e.g. a
 campus Wi-Fi AP) via VPN, and access to the Internet via NAT.  The
-gate machine uses three network interfaces (see <a href="#org40481f7">The Gate Machine</a>)
+gate machine uses three network interfaces (see <a href="#org817fd99">The Gate Machine</a>)
 configured with persistent names used in its firewall rules.
 </p>
 
@@ -5512,8 +5297,8 @@ applied first, by which Gate gets a campus machine's DNS and Postfix
 configurations, etc.
 </p>
 </div>
-<div id="outline-container-orge1f832f" class="outline-3">
-<h3 id="orge1f832f"><span class="section-number-3">9.1.</span> Include Particulars</h3>
+<div id="outline-container-org61c66db" class="outline-3">
+<h3 id="org61c66db"><span class="section-number-3">9.1.</span> Include Particulars</h3>
 <div class="outline-text-3" id="text-9-1">
 <p>
 The following should be familiar boilerplate by now.
@@ -5534,8 +5319,8 @@ The following should be familiar boilerplate by now.
 </div>
 </div>
 </div>
-<div id="outline-container-org73026e0" class="outline-3">
-<h3 id="org73026e0"><span class="section-number-3">9.2.</span> Configure Netplan <a id="orgfd4af3f"></a></h3>
+<div id="outline-container-orgf42ab1d" class="outline-3">
+<h3 id="orgf42ab1d"><span class="section-number-3">9.2.</span> Configure Netplan <a id="org2c03bc2"></a></h3>
 <div class="outline-text-3" id="text-9-2">
 <p>
 Gate's network interfaces are configured using Netplan and two files.
@@ -5584,7 +5369,7 @@ new network plan.
               addresses: [ {{ core_addr }} ]
               search: [ {{ domain_priv }} ]
             routes:
-              - to: {{ public_vpn_net_cidr }}
+              - to: {{ public_wg_net_cidr }}
                 via: {{ core_addr }}
           wild:
             match:
@@ -5631,8 +5416,8 @@ campus ISP without interference from Ansible.
 </p>
 </div>
 </div>
-<div id="outline-container-orge89b196" class="outline-3">
-<h3 id="orge89b196"><span class="section-number-3">9.3.</span> UFW Rules</h3>
+<div id="outline-container-org5f6e57c" class="outline-3">
+<h3 id="org5f6e57c"><span class="section-number-3">9.3.</span> UFW Rules</h3>
 <div class="outline-text-3" id="text-9-3">
 <p>
 Gate uses the Uncomplicated FireWall (UFW) to install its packet
@@ -5642,8 +5427,8 @@ expect to be able to exercise experimental services on random ports.
 The default policy settings in <q>/etc/default/ufw</q> are <code>ACCEPT</code> and
 <code>ACCEPT</code> for input and output, and <code>DROP</code> for forwarded packets.
 Forwarding was enabled in the kernel previously (when configuring
-OpenVPN) using Ansible's <code>sysctl</code> module.  It does not need to be set
-in <q>/etc/ufw/sysctl.conf</q>.
+WireGuard™) using Ansible's <code>sysctl</code> module.  It does not need to be
+set in <q>/etc/ufw/sysctl.conf</q>.
 </p>
 
 <p>
@@ -5656,7 +5441,7 @@ should not be routing their Internet traffic through their VPN.
 </p>
 
 <div class="org-src-container">
-<code>ufw-nat</code><pre class="src src-conf" id="org2b6b37d"><code>-A POSTROUTING -s {{ private_net_cidr }} -o isp -j MASQUERADE
+<code>ufw-nat</code><pre class="src src-conf" id="org321b8da"><code>-A POSTROUTING -s {{ private_net_cidr }} -o isp -j MASQUERADE
 -A POSTROUTING -s {{    wild_net_cidr }} -o isp -j MASQUERADE
 </code></pre>
 </div>
@@ -5671,7 +5456,7 @@ packet).
 </p>
 
 <div class="org-src-container">
-<code>ufw-forward-nat</code><pre class="src src-conf" id="org63ab446"><code>-A FORWARD -i lan  -o isp  -j ACCEPT
+<code>ufw-forward-nat</code><pre class="src src-conf" id="org8de8823"><code>-A FORWARD -i lan  -o isp  -j ACCEPT
 -A FORWARD -i wild -o isp  -j ACCEPT
 -A FORWARD -i isp  -o lan  {{ ACCEPT_RELATED }}
 -A FORWARD -i isp  -o wild {{ ACCEPT_RELATED }}
@@ -5696,26 +5481,26 @@ know!
 
 <p>
 Forwarding rules are also needed to route packets from the campus VPN
-(the <code>ovpn</code> tunnel device) to the institute's LAN and back.  The
-public VPN on Front will also be included since its packets arrive at
-Gate's <code>lan</code> interface, coming from Core.  Thus forwarding between
+(the <code>wg0</code> WireGuard™ tunnel device) to the institute's LAN and back.
+The public VPN on Front will also be included since its packets arrive
+at Gate's <code>lan</code> interface, coming from Core.  Thus forwarding between
 public and campus VPNs is also allowed.
 </p>
 
 <div class="org-src-container">
-<code>ufw-forward-private</code><pre class="src src-conf" id="org9b9c5a4"><code>-A FORWARD -i lan  -o ovpn -j ACCEPT
--A FORWARD -i ovpn -o lan  -j ACCEPT
+<code>ufw-forward-private</code><pre class="src src-conf" id="org269a64d"><code>-A FORWARD -i lan  -o wg0  -j ACCEPT
+-A FORWARD -i wg0  -o lan  -j ACCEPT
 </code></pre>
 </div>
 
 <p>
 Note that there are no forwarding rules to allow packets to pass from
-the <code>wild</code> device to the <code>lan</code> device, just the <code>ovpn</code> device.
+the <code>wild</code> device to the <code>lan</code> device, just the <code>wg0</code> device.
 </p>
 </div>
 </div>
-<div id="outline-container-org036ac74" class="outline-3">
-<h3 id="org036ac74"><span class="section-number-3">9.4.</span> Install UFW</h3>
+<div id="outline-container-org16439c8" class="outline-3">
+<h3 id="org16439c8"><span class="section-number-3">9.4.</span> Install UFW</h3>
 <div class="outline-text-3" id="text-9-4">
 <p>
 The following tasks install the Uncomplicated Firewall (UFW), set its
@@ -5775,8 +5560,8 @@ sudo ufw enable
 </div>
 </div>
 </div>
-<div id="outline-container-org43b50b8" class="outline-3">
-<h3 id="org43b50b8"><span class="section-number-3">9.5.</span> Configure DHCP For The Wild Ethernet</h3>
+<div id="outline-container-org6599d39" class="outline-3">
+<h3 id="org6599d39"><span class="section-number-3">9.5.</span> Configure DHCP For The Wild Ethernet</h3>
 <div class="outline-text-3" id="text-9-5">
 <p>
 To accommodate commodity Wi-Fi access points, as well as wired IoT
@@ -5902,146 +5687,132 @@ command would not be necessary.
 </p>
 </div>
 </div>
-<div id="outline-container-org435e716" class="outline-3">
-<h3 id="org435e716"><span class="section-number-3">9.6.</span> Install Server Certificate</h3>
+<div id="outline-container-org3e594c1" class="outline-3">
+<h3 id="org3e594c1"><span class="section-number-3">9.6.</span> Configure Campus WireGuard™ Subnet</h3>
 <div class="outline-text-3" id="text-9-6">
 <p>
-The (OpenVPN) server on Gate uses an institute certificate (and key)
-to authenticate itself to its clients.  It uses the <q>/etc/server.crt</q>
-and <q>/etc/server.key</q> files just because the other servers (on Core
-and Front) do.
+Gate uses WireGuard™ to provide a campus VPN service.  Gate's routes
+and firewall rules allow packets to be forwarded to/from the
+institute's private networks: the private Ethernet and the public VPN.
+(It should <i>not</i> forward packets to/from the wild Ethernet.)  The only
+additional route Gate needs is to the public VPN via Core.  The rest
+(private Ethernet and campus VPN) are directly connected.
+</p>
+
+<p>
+The following example <a href="#org29145ff"><q>private/gate-wg0.conf</q></a> configuration recognizes
+a wired IoT appliance, Dick's notebook and his replacement phone,
+assigning them the host numbers 3, 4 and 6 respectively.
 </p>
 
 <div class="org-src-container">
-<a href="roles_t/gate/tasks/main.yml"><q>roles_t/gate/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Install server certificate/key.
-  become: yes
-  copy:
-    src: ../Secret/CA/pki/{{ item.path }}.{{ item.typ }}
-    dest: /etc/server.{{ item.typ }}
-    mode: <span class="org-string">"{{ item.mode }}"</span>
-  loop:
-  - { path: <span class="org-string">"issued/gate.{{ domain_priv }}"</span>, typ: crt,
-      mode: <span class="org-string">"u=r,g=r,o=r"</span> }
-  - { path: <span class="org-string">"private/gate.{{ domain_priv }}"</span>, typ: key,
-      mode: <span class="org-string">"u=r,g=,o="</span> }
-  notify: Restart OpenVPN.
+<q>private/gate-wg0.conf</q><pre class="src src-conf" id="org29145ff"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.84.139.1/24
+<span class="org-variable-name">ListenPort</span> = 51820
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">thing</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = LdsCsgfjKCfd5+VKS+Q/dQhWO8NRNygByDO2VxbXlSQ=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.3
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">dick</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = 4qd4xdRztZBKhFrX9jI/b4fnMzpKQ5qhg691hwYSsX8=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.4
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">dicks-razr</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = zho0qMxoLclJSQu4GeJEcMkk0hx4Q047OcNc8vOejVw=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.6
 </code></pre>
 </div>
-</div>
-</div>
-<div id="outline-container-org877d1b2" class="outline-3">
-<h3 id="org877d1b2"><span class="section-number-3">9.7.</span> Configure OpenVPN</h3>
-<div class="outline-text-3" id="text-9-7">
+
 <p>
-Gate uses OpenVPN to provide the institute's campus VPN service.  Its
-clients are <i>not</i> configured to route <i>all</i> of their traffic through
-the VPN, so Gate pushes routes to the other institute networks.  Gate
-itself is on the private Ethernet and thereby learns about the route
-to Front.
+The configuration used on <code>thing</code>, the IoT appliance, looks like this:
 </p>
 
 <div class="org-src-container">
-<code>openvpn-gate-routes</code><pre class="src src-conf" id="org20aef17"><code>push <span class="org-string">"route {{ private_net_and_mask }}"</span>
-push <span class="org-string">"route {{ public_vpn_net_and_mask }}"</span>
+WireGuard™ tunnel on an IoT appliance<pre class="src src-conf"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.84.139.2
+<span class="org-variable-name">PrivateKey</span> = KIwQT5eGOl9w1qOa5I+2xx5kJH3z4xdpmirS/eGdsXY=
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Gate</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = y3cjFnvQbylmH4lGTujpqc8rusIElmJ4Gu9hh6iR7QI=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.1
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.0/24
+<span class="org-variable-name">AllowedIPs</span> = 192.168.56.0/24
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.0/24
 </code></pre>
 </div>
 
 <p>
-The complete OpenVPN configuration for Gate includes a <code>server</code>
-option, the pushed routes mentioned above, and the common options
-discussed in <a href="#org1ea6412">The VPN Services</a>.
+And the configuration used on Dick's notebook when it is on campus
+looks like this:
 </p>
 
 <div class="org-src-container">
-<code>openvpn-gate</code><pre class="src src-conf" id="orgd61aa9a"><code>server {{ campus_vpn_net_and_mask }}
-client-config-dir /etc/openvpn/ccd
-&lt;&lt;openvpn-gate-routes&gt;&gt;
-&lt;&lt;openvpn-dev-mode&gt;&gt;
-&lt;&lt;openvpn-keepalive&gt;&gt;
-&lt;&lt;openvpn-dns&gt;&gt;
-&lt;&lt;openvpn-drop-priv&gt;&gt;
-&lt;&lt;openvpn-crypt&gt;&gt;
-&lt;&lt;openvpn-max&gt;&gt;
-&lt;&lt;openvpn-debug&gt;&gt;
-ca /usr/local/share/ca-certificates/{{ domain_name }}.crt
-cert /etc/server.crt
-key /etc/server.key
-dh dh2048.pem
-tls-crypt shared.key
+WireGuard™ tunnel on Dick's notebook, used on campus<pre class="src src-conf"><code>[<span class="org-type">Interface</span>]
+<span class="org-variable-name">Address</span> = 10.84.139.3
+<span class="org-variable-name">PostUp</span> = wg set %i private-key /etc/wireguard/private-key
+<span class="org-variable-name">PostUp</span> = resolvectl dns wg0 192.168.56.1
+<span class="org-variable-name">PostUp</span> = resolvectl domain wg0 small.private
+
+<span class="org-comment-delimiter"># </span><span class="org-comment">Gate</span>
+[<span class="org-type">Peer</span>]
+<span class="org-variable-name">PublicKey</span> = y3cjFnvQbylmH4lGTujpqc8rusIElmJ4Gu9hh6iR7QI=
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.1
+<span class="org-variable-name">AllowedIPs</span> = 10.84.139.0/24
+<span class="org-variable-name">AllowedIPs</span> = 192.168.56.0/24
+<span class="org-variable-name">AllowedIPs</span> = 10.177.87.0/24
 </code></pre>
 </div>
 
 <p>
-Finally, here are the tasks (and handler) required to install and
-configure the OpenVPN server on Gate.
+The following tasks install WireGuard™, configure it with
+<a href="#org29145ff"><q>private/gate-wg0.conf</q></a>, and enable the service.
 </p>
 
 <div class="org-src-container">
 <a href="roles_t/gate/tasks/main.yml"><q>roles_t/gate/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Install OpenVPN.
-  become: yes
-  <span class="org-variable-name">apt: pkg</span>=openvpn
-
-- name: Enable IP forwarding.
+- name: Install WireGuard&#8482;.
   become: yes
-  sysctl:
-    name: net.ipv4.ip_forward
-    value: <span class="org-string">"1"</span>
-    state: present
+  <span class="org-variable-name">apt: pkg</span>=wireguard
 
-- name: Create OpenVPN client configuration directory.
-  become: yes
-  file:
-    path: /etc/openvpn/ccd
-    state: directory
-  notify: Restart OpenVPN.
-
-- name: Disable former VPN clients.
-  become: yes
-  copy:
-    content: <span class="org-string">"disable\n"</span>
-    dest: /etc/openvpn/ccd/{{ item }}
-  loop: <span class="org-string">"{{ revoked }}"</span>
-  notify: Restart OpenVPN.
-  tags: accounts
-
-- name: Install OpenVPN secrets.
+- name: Configure WireGuard&#8482;.
   become: yes
   copy:
-    src: ../Secret/{{ item.src }}
-    dest: /etc/openvpn/{{ item.dest }}
+    src: ../private/gate-wg0.conf
+    dest: /etc/wireguard/wg0.conf
     <span class="org-variable-name">mode: u</span>=r,g=,o=
-  loop:
-  - { src: gate-dh2048.pem, dest: dh2048.pem }
-  - { src: gate-shared.key, dest: shared.key }
-  notify: Restart OpenVPN.
+    owner: root
+    group: root
+  notify: Restart WireGuard&#8482;.
 
-- name: Configure OpenVPN.
+- name: Enable/Start WireGuard&#8482; on boot.
   become: yes
-  copy:
-    content: |
-      &lt;&lt;openvpn-gate&gt;&gt;
-    dest: /etc/openvpn/server.conf
-    <span class="org-variable-name">mode: u</span>=r,g=r,o=
-  notify: Restart OpenVPN.
+  systemd:
+    service: wg-quick@wg0
+    enabled: yes
+    state: started
 </code></pre>
 </div>
 
 <div class="org-src-container">
 <a href="roles_t/gate/handlers/main.yml"><q>roles_t/gate/handlers/main.yml</q></a><pre class="src src-conf"><code>
-- name: Restart OpenVPN.
+- name: Restart WireGuard&#8482;.
   become: yes
   systemd:
-    service: openvpn@server
+    service: wg-quick@wg0
     state: restarted
 </code></pre>
 </div>
 </div>
 </div>
 </div>
-<div id="outline-container-orgb271096" class="outline-2">
-<h2 id="orgb271096"><span class="section-number-2">10.</span> The Campus Role</h2>
+<div id="outline-container-org5a28d65" class="outline-2">
+<h2 id="org5a28d65"><span class="section-number-2">10.</span> The Campus Role</h2>
 <div class="outline-text-2" id="text-10">
 <p>
 The <code>campus</code> role configures generic campus server machines: network
@@ -6053,13 +5824,12 @@ system administrator's account on Core.
 </p>
 
 <p>
-Wireless campus devices can get a key to the campus VPN from the
-<code>./inst client campus</code> command, but their OpenVPN client must be
-configured manually.
+Wireless campus devices register their public keys using the <code>./inst
+client</code> command which updates the WireGuard™ configuration on Gate.
 </p>
 </div>
-<div id="outline-container-orgeb3c56c" class="outline-3">
-<h3 id="orgeb3c56c"><span class="section-number-3">10.1.</span> Include Particulars</h3>
+<div id="outline-container-org75e6c6b" class="outline-3">
+<h3 id="org75e6c6b"><span class="section-number-3">10.1.</span> Include Particulars</h3>
 <div class="outline-text-3" id="text-10-1">
 <p>
 The following should be familiar boilerplate by now.
@@ -6075,8 +5845,8 @@ The following should be familiar boilerplate by now.
 </div>
 </div>
 </div>
-<div id="outline-container-org6c045dd" class="outline-3">
-<h3 id="org6c045dd"><span class="section-number-3">10.2.</span> Configure Hostname</h3>
+<div id="outline-container-orgb180c5e" class="outline-3">
+<h3 id="orgb180c5e"><span class="section-number-3">10.2.</span> Configure Hostname</h3>
 <div class="outline-text-3" id="text-10-2">
 <p>
 Clients should be using the expected host name.
@@ -6103,52 +5873,10 @@ Clients should be using the expected host name.
 </div>
 </div>
 </div>
-<div id="outline-container-orgb5ed705" class="outline-3">
-<h3 id="orgb5ed705"><span class="section-number-3">10.3.</span> Configure Systemd Resolved</h3>
+<div id="outline-container-orgfa8d1f7" class="outline-3">
+<h3 id="orgfa8d1f7"><span class="section-number-3">10.3.</span> Configure Systemd Timesyncd</h3>
 <div class="outline-text-3" id="text-10-3">
 <p>
-Campus machines use the campus name server on Core (or <code>dns.google</code>),
-and include the institute's private domain in their search lists.
-</p>
-
-<div class="org-src-container">
-<a href="roles_t/campus/tasks/main.yml"><q>roles_t/campus/tasks/main.yml</q></a><pre class="src src-conf"><code>
-- name: Configure resolved.
-  become: yes
-  lineinfile:
-    path: /etc/systemd/resolved.conf
-    regexp: <span class="org-string">"{{ item.regexp }}"</span>
-    line: <span class="org-string">"{{ item.line }}"</span>
-  loop:
-  - { regexp: <span class="org-string">'^ *DNS *='</span>, line: <span class="org-string">"DNS={{ core_addr }}"</span> }
-  - { regexp: <span class="org-string">'^ *FallbackDNS *='</span>, line: <span class="org-string">"FallbackDNS=8.8.8.8"</span> }
-  - { regexp: <span class="org-string">'^ *Domains *='</span>, line: <span class="org-string">"Domains={{ domain_priv }}"</span> }
-  notify:
-  - Reload Systemd.
-  - Restart Systemd resolved.
-</code></pre>
-</div>
-
-<div class="org-src-container">
-<a href="roles_t/campus/handlers/main.yml"><q>roles_t/campus/handlers/main.yml</q></a><pre class="src src-conf"><code>---
-- name: Reload Systemd.
-  become: yes
-  systemd:
-    daemon-reload: yes
-
-- name: Restart Systemd resolved.
-  become: yes
-  systemd:
-    service: systemd-resolved
-    state: restarted
-</code></pre>
-</div>
-</div>
-</div>
-<div id="outline-container-orgc9318c9" class="outline-3">
-<h3 id="orgc9318c9"><span class="section-number-3">10.4.</span> Configure Systemd Timesyncd</h3>
-<div class="outline-text-3" id="text-10-4">
-<p>
 The institute uses a common time reference throughout the campus.
 This is essential to campus security, improving the accuracy of log
 and file timestamps.
@@ -6166,7 +5894,7 @@ and file timestamps.
 </div>
 
 <div class="org-src-container">
-<a href="roles_t/campus/handlers/main.yml"><q>roles_t/campus/handlers/main.yml</q></a><pre class="src src-conf"><code>
+<a href="roles_t/campus/handlers/main.yml"><q>roles_t/campus/handlers/main.yml</q></a><pre class="src src-conf"><code>---
 - name: Restart systemd-timesyncd.
   become: yes
   systemd:
@@ -6176,9 +5904,9 @@ and file timestamps.
 </div>
 </div>
 </div>
-<div id="outline-container-org17d2e8d" class="outline-3">
-<h3 id="org17d2e8d"><span class="section-number-3">10.5.</span> Add Administrator to System Groups</h3>
-<div class="outline-text-3" id="text-10-5">
+<div id="outline-container-orgcf78de0" class="outline-3">
+<h3 id="orgcf78de0"><span class="section-number-3">10.4.</span> Add Administrator to System Groups</h3>
+<div class="outline-text-3" id="text-10-4">
 <p>
 The administrator often needs to read (directories of) log files owned
 by groups <code>root</code> and <code>adm</code>.  Adding the administrator's account to
@@ -6197,9 +5925,9 @@ these groups speeds up debugging.
 </div>
 </div>
 </div>
-<div id="outline-container-org1c6f15d" class="outline-3">
-<h3 id="org1c6f15d"><span class="section-number-3">10.6.</span> Install Unattended Upgrades</h3>
-<div class="outline-text-3" id="text-10-6">
+<div id="outline-container-org88b0bbb" class="outline-3">
+<h3 id="org88b0bbb"><span class="section-number-3">10.5.</span> Install Unattended Upgrades</h3>
+<div class="outline-text-3" id="text-10-5">
 <p>
 The institute prefers to install security updates as soon as possible.
 </p>
@@ -6213,9 +5941,9 @@ The institute prefers to install security updates as soon as possible.
 </div>
 </div>
 </div>
-<div id="outline-container-org990a25f" class="outline-3">
-<h3 id="org990a25f"><span class="section-number-3">10.7.</span> Configure Postfix on Campus</h3>
-<div class="outline-text-3" id="text-10-7">
+<div id="outline-container-org7856e77" class="outline-3">
+<h3 id="org7856e77"><span class="section-number-3">10.6.</span> Configure Postfix on Campus</h3>
+<div class="outline-text-3" id="text-10-6">
 <p>
 The Postfix settings used by the campus include message size, queue
 times, and the <code>relayhost</code> Core.  The default Debian configuration
@@ -6275,9 +6003,9 @@ tasks below.
 </div>
 </div>
 </div>
-<div id="outline-container-org2b656fb" class="outline-3">
-<h3 id="org2b656fb"><span class="section-number-3">10.8.</span> Set Domain Name</h3>
-<div class="outline-text-3" id="text-10-8">
+<div id="outline-container-org41d6f54" class="outline-3">
+<h3 id="org41d6f54"><span class="section-number-3">10.7.</span> Set Domain Name</h3>
+<div class="outline-text-3" id="text-10-7">
 <p>
 The host's fully qualified (private) domain name (FQDN) is set by an
 alias in its <q>/etc/hosts</q> file, as is customary on Debian.  (See "The
@@ -6299,13 +6027,13 @@ manpage.)
 </div>
 </div>
 </div>
-<div id="outline-container-org356ce5d" class="outline-3">
-<h3 id="org356ce5d"><span class="section-number-3">10.9.</span> Configure NRPE</h3>
-<div class="outline-text-3" id="text-10-9">
+<div id="outline-container-orgc703c4f" class="outline-3">
+<h3 id="orgc703c4f"><span class="section-number-3">10.8.</span> Configure NRPE</h3>
+<div class="outline-text-3" id="text-10-8">
 <p>
 Each campus host runs an NRPE (a NAGIOS Remote Plugin Executor)
 server so that the NAGIOS4 server on Core can collect statistics.  The
-NAGIOS service is discussed in the <a href="#org356ce5d">Configure NRPE</a> section of <a href="#orge6d718e">The Core
+NAGIOS service is discussed in the <a href="#orgc703c4f">Configure NRPE</a> section of <a href="#org03278f3">The Core
 Role</a>.
 </p>
 
@@ -6359,8 +6087,8 @@ Role</a>.
 </div>
 </div>
 </div>
-<div id="outline-container-orgda31017" class="outline-2">
-<h2 id="orgda31017"><span class="section-number-2">11.</span> The Ansible Configuration</h2>
+<div id="outline-container-org9259070" class="outline-2">
+<h2 id="org9259070"><span class="section-number-2">11.</span> The Ansible Configuration</h2>
 <div class="outline-text-2" id="text-11">
 <p>
 The small institute uses Ansible to maintain the configuration of its
@@ -6369,7 +6097,7 @@ runs playbook <a href="playbook/site.yml"><q>site.yml</q></a> to apply the appro
 role(s) to each host.  Examples of these files are included here, and
 are used to test the roles.  The example configuration applies the
 institutional roles to VirtualBox machines prepared according to
-chapter <a href="#org7832c43">Testing</a>.
+chapter <a href="#org3d2b7a1">Testing</a>.
 </p>
 
 <p>
@@ -6382,13 +6110,13 @@ while changes to the institute's particulars are committed to a
 separate revision history.
 </p>
 </div>
-<div id="outline-container-orgf6e5306" class="outline-3">
-<h3 id="orgf6e5306"><span class="section-number-3">11.1.</span> <q>ansible.cfg</q></h3>
+<div id="outline-container-org87df2ac" class="outline-3">
+<h3 id="org87df2ac"><span class="section-number-3">11.1.</span> <q>ansible.cfg</q></h3>
 <div class="outline-text-3" id="text-11-1">
 <p>
 The Ansible configuration file <a href="ansible.cfg"><q>ansible.cfg</q></a> contains just a handful
 of settings, some included just to create a test jig as described in
-<a href="#org7832c43">Testing</a>.
+<a href="#org3d2b7a1">Testing</a>.
 </p>
 
 <ul class="org-ul">
@@ -6397,7 +6125,7 @@ of settings, some included just to create a test jig as described in
 that Python 3 can be expected on all institute hosts.</li>
 <li><code>vault_password_file</code> is set to suppress prompts for the vault
 password.  The institute keeps its vault password in <a href="Secret/"><q>Secret/</q></a> (as
-described in <a href="#org1c1be46">Keys</a>) and thus sets this parameter to
+described in <a href="#org8244014">Keys</a>) and thus sets this parameter to
 <a href="Secret/vault-password"><q>Secret/vault-password</q></a>.</li>
 <li><code>inventory</code> is set to avoid specifying it on the command line.</li>
 <li><code>roles_path</code> is set to the recently tangled roles files in
@@ -6414,8 +6142,8 @@ described in <a href="#org1c1be46">Keys</a>) and thus sets this parameter to
 </div>
 </div>
 </div>
-<div id="outline-container-orge7d3e33" class="outline-3">
-<h3 id="orge7d3e33"><span class="section-number-3">11.2.</span> <q>hosts</q></h3>
+<div id="outline-container-org860d201" class="outline-3">
+<h3 id="org860d201"><span class="section-number-3">11.2.</span> <q>hosts</q></h3>
 <div class="outline-text-3" id="text-11-2">
 <p>
 The Ansible inventory file <a href="hosts"><q>hosts</q></a> describes all of the institute's
@@ -6427,7 +6155,7 @@ describes three test servers named <code>front</code>, <code>core</code> and <co
 </p>
 
 <div class="org-src-container">
-<a href="hosts"><q>hosts</q></a><pre class="src src-conf" id="org0b95976"><code>all:
+<a href="hosts"><q>hosts</q></a><pre class="src src-conf" id="org0363454"><code>all:
   vars:
     ansible_user: sysadm
     ansible_ssh_extra_args: -i Secret/ssh_admin/id_rsa
@@ -6492,8 +6220,8 @@ the <a href="Secret/vault-password"><q>Secret/vault-password</q></a> file.
 </p>
 </div>
 </div>
-<div id="outline-container-org723c67b" class="outline-3">
-<h3 id="org723c67b"><span class="section-number-3">11.3.</span> <q>playbooks/site.yml</q></h3>
+<div id="outline-container-orgf62dd32" class="outline-3">
+<h3 id="orgf62dd32"><span class="section-number-3">11.3.</span> <q>playbooks/site.yml</q></h3>
 <div class="outline-text-3" id="text-11-3">
 <p>
 The example <a href="playbooks/site.yml"><q>playbooks/site.yml</q></a> playbook (below) applies the
@@ -6526,8 +6254,8 @@ the example inventory: <a href="hosts"><q>hosts</q></a>.
 </div>
 </div>
 </div>
-<div id="outline-container-org696cc09" class="outline-3">
-<h3 id="org696cc09"><span class="section-number-3">11.4.</span> <q>Secret/vault-password</q></h3>
+<div id="outline-container-org2ed44a5" class="outline-3">
+<h3 id="org2ed44a5"><span class="section-number-3">11.4.</span> <q>Secret/vault-password</q></h3>
 <div class="outline-text-3" id="text-11-4">
 <p>
 As already mentioned, the small institute keeps its Ansible vault
@@ -6539,17 +6267,17 @@ example password matches the example encryptions above.
 </p>
 
 <div class="org-src-container">
-<a href="Secret/vault-password"><q>Secret/vault-password</q></a><pre class="src src-conf" id="org442ebbb"><code>alitysortstagess
+<a href="Secret/vault-password"><q>Secret/vault-password</q></a><pre class="src src-conf" id="org6485306"><code>alitysortstagess
 </code></pre>
 </div>
 </div>
 </div>
-<div id="outline-container-org93d3d06" class="outline-3">
-<h3 id="org93d3d06"><span class="section-number-3">11.5.</span> Creating A Working Ansible Configuration</h3>
+<div id="outline-container-org36f0380" class="outline-3">
+<h3 id="org36f0380"><span class="section-number-3">11.5.</span> Creating A Working Ansible Configuration</h3>
 <div class="outline-text-3" id="text-11-5">
 <p>
 A working Ansible configuration can be "tangled" from this document to
-produce the test configuration described in the <a href="#org7832c43">Testing</a> chapter.  The
+produce the test configuration described in the <a href="#org3d2b7a1">Testing</a> chapter.  The
 tangling is done by Emacs's <code>org-babel-tangle</code> function and has
 already been performed with the resulting tangle included in the
 distribution with this document.
@@ -6592,7 +6320,7 @@ would be copied, with appropriate changes, into new subdirectories
 <q>public/</q> and <q>private/</q>.</li>
 <li><q>~/net/Secret</q> would be a symbolic link to the (auto-mounted?)
 location of the administrator's encrypted USB drive, as described in
-section <a href="#org1c1be46">Keys</a>.</li>
+section <a href="#org8244014">Keys</a>.</li>
 </ul>
 
 <p>
@@ -6628,8 +6356,8 @@ super-project's directory.
 </div>
 </div>
 </div>
-<div id="outline-container-orgb3fb614" class="outline-3">
-<h3 id="orgb3fb614"><span class="section-number-3">11.6.</span> Maintaining A Working Ansible Configuration</h3>
+<div id="outline-container-org29e9a55" class="outline-3">
+<h3 id="org29e9a55"><span class="section-number-3">11.6.</span> Maintaining A Working Ansible Configuration</h3>
 <div class="outline-text-3" id="text-11-6">
 <p>
 The Ansible roles currently tangle into the <a href="roles_t/"><q>roles_t/</q></a> directory to
@@ -6648,8 +6376,8 @@ their way back to the code block in this document.
 </div>
 </div>
 </div>
-<div id="outline-container-org5db480b" class="outline-2">
-<h2 id="org5db480b"><span class="section-number-2">12.</span> The Institute Commands</h2>
+<div id="outline-container-org1eb4766" class="outline-2">
+<h2 id="org1eb4766"><span class="section-number-2">12.</span> The Institute Commands</h2>
 <div class="outline-text-2" id="text-12">
 <p>
 The institute's administrator uses a convenience script to reliably
@@ -6659,8 +6387,8 @@ Ansible configuration.  The Ansible commands it executes are expected
 to get their defaults from <q>./ansible.cfg</q>.
 </p>
 </div>
-<div id="outline-container-orgad96cbc" class="outline-3">
-<h3 id="orgad96cbc"><span class="section-number-3">12.1.</span> Sub-command Blocks</h3>
+<div id="outline-container-org521c9c6" class="outline-3">
+<h3 id="org521c9c6"><span class="section-number-3">12.1.</span> Sub-command Blocks</h3>
 <div class="outline-text-3" id="text-12-1">
 <p>
 The code blocks in this chapter tangle into the <a href="inst"><q>inst</q></a> script.  Each
@@ -6684,8 +6412,8 @@ The first code block is the header of the <code>./inst</code> script.
 </div>
 </div>
 </div>
-<div id="outline-container-org3b3c0fd" class="outline-3">
-<h3 id="org3b3c0fd"><span class="section-number-3">12.2.</span> Sanity Check</h3>
+<div id="outline-container-org2762906" class="outline-3">
+<h3 id="org2762906"><span class="section-number-3">12.2.</span> Sanity Check</h3>
 <div class="outline-text-3" id="text-12-2">
 <p>
 The next code block does not implement a sub-command; it implements
@@ -6745,8 +6473,8 @@ permissions.  It probes past the <a href="Secret/"><q>Secret/</q></a> mount poin
 </div>
 </div>
 </div>
-<div id="outline-container-org1a212b5" class="outline-3">
-<h3 id="org1a212b5"><span class="section-number-3">12.3.</span> Importing Ansible Variables</h3>
+<div id="outline-container-org356264c" class="outline-3">
+<h3 id="org356264c"><span class="section-number-3">12.3.</span> Importing Ansible Variables</h3>
 <div class="outline-text-3" id="text-12-3">
 <p>
 To ensure that Ansible and <code>./inst</code> are sympatico vis-a-vi certain
@@ -6768,7 +6496,12 @@ them.
 
 mysystem <span class="org-string">"ansible-playbook playbooks/check-inst-vars.yml &gt;/dev/null"</span>;
 
-our ($domain_name, $domain_priv, $front_addr, $gate_wild_addr);
+our ($domain_name, $domain_priv, $private_net_cidr,
+     $front_addr, $front_wg_pubkey,
+     $public_wg_net_cidr, $public_wg_port,
+     $gate_wild_addr, $gate_wg_pubkey,
+     $campus_wg_net_cidr, $campus_wg_port,
+     $core_addr, $core_wg_pubkey);
 do <span class="org-string">"./private/vars.pl"</span>;
 </code></pre>
 </div>
@@ -6787,16 +6520,107 @@ The playbook that updates <a href="private/vars.pl"><q>private/vars.pl</q></a>:
       content: |
         <span class="org-variable-name">$domain_name</span> = <span class="org-string">"{{ domain_name }}"</span>;
         <span class="org-variable-name">$domain_priv</span> = <span class="org-string">"{{ domain_priv }}"</span>;
+        <span class="org-variable-name">$private_net_cidr</span> = <span class="org-string">"{{ private_net_cidr }}"</span>;
+
         <span class="org-variable-name">$front_addr</span> = <span class="org-string">"{{ front_addr }}"</span>;
+        <span class="org-variable-name">$front_wg_pubkey</span> = <span class="org-string">"{{ front_wg_pubkey }}"</span>;
+
+        <span class="org-variable-name">$public_wg_net_cidr</span> = <span class="org-string">"{{ public_wg_net_cidr }}"</span>;
+
+        <span class="org-variable-name">$public_wg_port</span> = <span class="org-string">"{{ public_wg_port }}"</span>;
+
         <span class="org-variable-name">$gate_wild_addr</span> = <span class="org-string">"{{ gate_wild_addr }}"</span>;
+        <span class="org-variable-name">$gate_wg_pubkey</span> = <span class="org-string">"{{ gate_wg_pubkey }}"</span>;
+
+        <span class="org-variable-name">$campus_wg_net_cidr</span> = <span class="org-string">"{{ campus_wg_net_cidr }}"</span>;
+        <span class="org-variable-name">$campus_wg_port</span> = <span class="org-string">"{{ campus_wg_port }}"</span>;
+
+        <span class="org-variable-name">$core_addr</span> = <span class="org-string">"{{ core_addr }}"</span>;
+        <span class="org-variable-name">$core_wg_pubkey</span> = <span class="org-string">"{{ core_wg_pubkey }}"</span>;
       dest: ../private/vars.pl
       <span class="org-variable-name">mode: u</span>=rw,g=,o=
 </code></pre>
 </div>
+
+<p>
+Most of these settings are already in <q>private/vars.yml</q>.  The
+following few provide the servers' public keys and ports.
+</p>
+
+<div class="org-src-container">
+<a href="private/vars.yml">=private/vars.yml</a><pre class="src src-conf"><code><span class="org-variable-name">front_wg_pubkey: S+6HaTnOwwhWgUGXjSBcPAvifKw+j8BDTRfq534gNW4</span>=
+public_wg_port:  39608
+
+<span class="org-variable-name">gate_wg_pubkey:  y3cjFnvQbylmH4lGTujpqc8rusIElmJ4Gu9hh6iR7QI</span>=
+campus_wg_port:  51820
+
+<span class="org-variable-name">core_wg_pubkey:  lGhC51IBgZtlq4H2bsYFuKvPtV0VAEwUvVIn5fW7D0c</span>=
+</code></pre>
+</div>
+
+<p>
+All of the private keys used in the example/test configuration are
+listed in the following table.  The first three are copied to
+<q>/etc/wireguard/private-key</q> on each of the corresponding test
+machines: <code>front</code>, <code>gate</code> and <code>core</code>.  The rest are installed on
+the test client to give it different personae.
+</p>
+
+<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
+
+
+<colgroup>
+<col  class="org-left" />
+
+<col  class="org-left" />
+</colgroup>
+<thead>
+<tr>
+<th scope="col" class="org-left">Test Host</th>
+<th scope="col" class="org-left">WireGuard™ Private Key</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td class="org-left"><code>front</code></td>
+<td class="org-left">AJkzVxfTm/KvRjzTN/9X2jYy+CAugiwZfN5F3JTegms=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>gate</code></td>
+<td class="org-left">yOBdLbXh6KBwYQvvb5mhiku8Fxkqc5Cdyz6gNgjc/2U=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>core</code></td>
+<td class="org-left">AI+KhwnsHzSPqyIyAObx7EBBTBXFZPiXb2/Qcts8zEI=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>thing</code></td>
+<td class="org-left">KIwQT5eGOl9w1qOa5I+2xx5kJH3z4xdpmirS/eGdsXY=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>dick</code></td>
+<td class="org-left">WAhrlGccPf/BaFS5bRtBE4hEyt3kDxCavmwZfVTsfGs=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>dicks-phone</code></td>
+<td class="org-left">oG/Kou9HOBCBwHAZGypPA1cZWUL6nR6WoxBiXc/OQWQ=</td>
+</tr>
+
+<tr>
+<td class="org-left"><code>dicks-razr</code></td>
+<td class="org-left">IGNcF0VpkIBcJQAcLZ9jgRmk0SYyUr/WwSNXZoXXUWQ=</td>
+</tr>
+</tbody>
+</table>
 </div>
 </div>
-<div id="outline-container-org3fe9e37" class="outline-3">
-<h3 id="org3fe9e37"><span class="section-number-3">12.4.</span> The CA Command</h3>
+<div id="outline-container-orgd14a881" class="outline-3">
+<h3 id="orgd14a881"><span class="section-number-3">12.4.</span> The CA Command</h3>
 <div class="outline-text-3" id="text-12-4">
 <p>
 The next code block implements the <code>CA</code> sub-command, which creates a
@@ -6859,14 +6683,8 @@ config</code>.
   <span class="org-keyword">my</span> $<span class="org-variable-name">dom</span> = $<span class="org-variable-name">domain_name</span>;
   <span class="org-keyword">my</span> $<span class="org-variable-name">pvt</span> = $<span class="org-variable-name">domain_priv</span>;
   mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-server-full $dom nopass"</span>;
-  mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-server-full gate.$pvt nopass"</span>;
   mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-server-full core.$pvt nopass"</span>;
-  mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-client-full core nopass"</span>;
   umask 077;
-  mysystem <span class="org-string">"openvpn --genkey secret Secret/front-shared.key"</span>;
-  mysystem <span class="org-string">"openvpn --genkey secret Secret/gate-shared.key"</span>;
-  mysystem <span class="org-string">"openssl dhparam -out Secret/front-dh2048.pem 2048"</span>;
-  mysystem <span class="org-string">"openssl dhparam -out Secret/gate-dh2048.pem 2048"</span>;
 
   mysystem <span class="org-string">"mkdir --mode=700 Secret/root.gnupg"</span>;
   mysystem (<span class="org-string">"gpg --homedir Secret/root.gnupg"</span>,
@@ -6902,13 +6720,13 @@ config</code>.
 </div>
 </div>
 </div>
-<div id="outline-container-orge93c2cb" class="outline-3">
-<h3 id="orge93c2cb"><span class="section-number-3">12.5.</span> The Config Command</h3>
+<div id="outline-container-orgb6ae049" class="outline-3">
+<h3 id="orgb6ae049"><span class="section-number-3">12.5.</span> The Config Command</h3>
 <div class="outline-text-3" id="text-12-5">
 <p>
 The next code block implements the <code>config</code> sub-command, which
 provisions network services by running the <q>site.yml</q> playbook
-described in <a href="#org723c67b"><q>playbooks/site.yml</q></a>.  It recognizes an optional <code>-n</code>
+described in <a href="playbooks/site.yml"><q>playbooks/site.yml</q></a>.  It recognizes an optional <code>-n</code>
 flag indicating that the service configurations should just be
 checked.  Given an optional host name, it provisions (or checks) just
 the named host.
@@ -6953,12 +6771,12 @@ Example command lines:
 </div>
 </div>
 </div>
-<div id="outline-container-org6138e49" class="outline-3">
-<h3 id="org6138e49"><span class="section-number-3">12.6.</span> Account Management</h3>
+<div id="outline-container-org944d8f7" class="outline-3">
+<h3 id="org944d8f7"><span class="section-number-3">12.6.</span> Account Management</h3>
 <div class="outline-text-3" id="text-12-6">
 <p>
 For general information about members and their Unix accounts, see
-<a href="#orga38c5ed">Accounts</a>.  The account management sub-commands maintain a mapping
+<a href="#org03cae01">Accounts</a>.  The account management sub-commands maintain a mapping
 associating member "usernames" (Unix account names) with their
 records.  The mapping is stored among other things in
 <q>private/members.yml</q> as the value associated with the key <code>members</code>.
@@ -6969,17 +6787,19 @@ A new member's record in the <code>members</code> mapping will have the <code>st
 key value <code>current</code>.  That key gets value <code>former</code> when the member
 leaves.<sup><a id="fnr.3" class="footref" href="#fn.3" role="doc-backlink">3</a></sup>  Access by former members is revoked by invalidating the
 Unix account passwords, removing any authorized SSH keys from Front
-and Core, and disabling their VPN certificates.
+and Core, and removing their public keys from the WireGuard™
+configurations.
 </p>
 
 <p>
 The example file (below) contains a membership roll with one
-membership record, for an account named <code>dick</code>, which was issued
-client certificates for devices named <code>dick-note</code>, <code>dick-phone</code> and
-<code>dick-razr</code>.  <code>dick-phone</code> appears to be lost because its certificate
-was revoked.  Dick's membership record includes a vault-encrypted
-password (for Fetchmail) and the two password hashes installed on
-Front and Core.  (The example hashes are truncated versions.)
+membership record, for an account named <code>dick</code>, which registered the
+public keys of devices named <code>dick</code>, <code>dicks-phone</code> and <code>dicks-razr</code>.
+<code>dicks-razr</code> is presumably a replacement for <code>dicks-phone</code>, which was
+lost and its key invalidated.  Lastly, Dick's membership record
+includes a vault-encrypted password (for Fetchmail) and the two
+password hashes installed on Front and Core.  (The example hashes are
+truncated versions.)
 </p>
 
 <div class="org-src-container">
@@ -6988,9 +6808,9 @@ members:
   dick:
     status: current
     clients:
-    - dick-note
-    - dick-phone
-    - dick-razr
+    <span class="org-variable-name">- dick 4 4qd4xdRztZBKhFrX9jI/b4fnMzpKQ5qhg691hwYSsX8</span>=
+    <span class="org-variable-name">- dicks-phone 5 --WFbTSff17QiYObXoU+7mjaEUCqKjgvLqA49pAxqVeWg</span>=
+    <span class="org-variable-name">- dicks-razr 6 zho0qMxoLclJSQu4GeJEcMkk0hx4Q047OcNc8vOejVw</span>=
     password_front:
       $6$17h49U76$c7TsH6eMVmoKElNANJU1F1LrRrqzYVDreNu.QarpCoSt9u0gTHgiQ
     password_core:
@@ -7004,8 +6824,8 @@ members:
       6535633263656434393030333032343533626235653332626330666166613833
 usernames:
 - dick
-revoked:
-- dick-phone
+clients:
+<span class="org-variable-name">- thing 3 LdsCsgfjKCfd5+VKS+Q/dQhWO8NRNygByDO2VxbXlSQ</span>=
 </code></pre>
 </div>
 
@@ -7021,7 +6841,7 @@ is used instead.
 <a href="private/members-empty.yml"><q>private/members-empty.yml</q></a><pre class="src src-conf"><code>---
 members:
 usernames: []
-revoked: []
+clients: []
 </code></pre>
 </div>
 
@@ -7083,7 +6903,7 @@ read from the file.  The dump subroutine is another story (below).
   <span class="org-keyword">if</span> (keys %{$<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"members"</span>}}) {
     print $<span class="org-variable-name">O</span> <span class="org-string">"members:\n"</span>;
     <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">user</span> (sort keys %{$<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"members"</span>}}) {
-      print_member ($<span class="org-variable-name">O</span>, $<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"members"</span>}-&gt;{$<span class="org-variable-name">user</span>});
+      print_member ($<span class="org-variable-name">O</span>, $<span class="org-variable-name">user</span>, $<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"members"</span>}-&gt;{$<span class="org-variable-name">user</span>});
     }
     print $<span class="org-variable-name">O</span> <span class="org-string">"usernames:\n"</span>;
     <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">user</span> (sort keys %{$<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"members"</span>}}) {
@@ -7093,13 +6913,13 @@ read from the file.  The dump subroutine is another story (below).
     print $<span class="org-variable-name">O</span> <span class="org-string">"members:\n"</span>;
     print $<span class="org-variable-name">O</span> <span class="org-string">"usernames: []\n"</span>;
   }
-  <span class="org-keyword">if</span> (@{$<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"revoked"</span>}}) {
-    print $<span class="org-variable-name">O</span> <span class="org-string">"revoked:\n"</span>;
-    <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">name</span> (@{$<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"revoked"</span>}}) {
+  <span class="org-keyword">if</span> (@{$<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"clients"</span>}}) {
+    print $<span class="org-variable-name">O</span> <span class="org-string">"clients:\n"</span>;
+    <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">name</span> (@{$<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"clients"</span>}}) {
       print $<span class="org-variable-name">O</span> <span class="org-string">"- $name\n"</span>;
     }
   } <span class="org-keyword">else</span> {
-    print $<span class="org-variable-name">O</span> <span class="org-string">"revoked: []\n"</span>;
+    print $<span class="org-variable-name">O</span> <span class="org-string">"clients: []\n"</span>;
   }
   close $<span class="org-variable-name">O</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not close $pathname: $!\n"</span>;
 }
@@ -7121,10 +6941,9 @@ each record.
 
 <div class="org-src-container">
 <a href="inst"><q>inst</q></a><pre class="src src-perl"><code>
-<span class="org-keyword">sub</span> <span class="org-function-name">print_member</span> ($$) {
-  <span class="org-keyword">my</span> ($<span class="org-variable-name">out</span>, $<span class="org-variable-name">member</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
-  print $<span class="org-variable-name">out</span> <span class="org-string">"  "</span>, $<span class="org-variable-name">member</span>-&gt;{<span class="org-string">"username"</span>}, <span class="org-string">":\n"</span>;
-  print $<span class="org-variable-name">out</span> <span class="org-string">"    username: "</span>, $<span class="org-variable-name">member</span>-&gt;{<span class="org-string">"username"</span>}, <span class="org-string">"\n"</span>;
+<span class="org-keyword">sub</span> <span class="org-function-name">print_member</span> ($$$) {
+  <span class="org-keyword">my</span> ($<span class="org-variable-name">out</span>, $<span class="org-variable-name">username</span>, $<span class="org-variable-name">member</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
+  print $<span class="org-variable-name">out</span> <span class="org-string">"  "</span>, $<span class="org-variable-name">username</span>, <span class="org-string">":\n"</span>;
   print $<span class="org-variable-name">out</span> <span class="org-string">"    status: "</span>, $<span class="org-variable-name">member</span>-&gt;{<span class="org-string">"status"</span>}, <span class="org-string">"\n"</span>;
   <span class="org-keyword">if</span> (@{$<span class="org-variable-name">member</span>-&gt;{<span class="org-string">"clients"</span>} || []}) {
     print $<span class="org-variable-name">out</span> <span class="org-string">"    clients:\n"</span>;
@@ -7142,7 +6961,7 @@ each record.
       print $<span class="org-variable-name">out</span> <span class="org-string">"      $line\n"</span>;
     }
   }
-  <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">standard_keys</span> = ( <span class="org-string">"username"</span>, <span class="org-string">"status"</span>, <span class="org-string">"clients"</span>,
+  <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">standard_keys</span> = ( <span class="org-string">"status"</span>, <span class="org-string">"clients"</span>,
                         <span class="org-string">"password_front"</span>, <span class="org-string">"password_core"</span>,
                         <span class="org-string">"password_fetchmail"</span> );
   <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">other_keys</span> = (sort
@@ -7157,8 +6976,8 @@ each record.
 </div>
 </div>
 </div>
-<div id="outline-container-org541678b" class="outline-3">
-<h3 id="org541678b"><span class="section-number-3">12.7.</span> The New Command</h3>
+<div id="outline-container-org7a31407" class="outline-3">
+<h3 id="org7a31407"><span class="section-number-3">12.7.</span> The New Command</h3>
 <div class="outline-text-3" id="text-12-7">
 <p>
 The next code block implements the <code>new</code> sub-command.  It adds a new
@@ -7192,14 +7011,11 @@ initial, generated password.
   mysystem (<span class="org-string">"ansible-playbook -e \@Secret/become.yml"</span>,
             <span class="org-string">" playbooks/nextcloud-new.yml"</span>,
             <span class="org-string">" -e user=$user"</span>, <span class="org-string">" -e pass=\"$epass\""</span>);
-  $<span class="org-variable-name">members</span>-&gt;{$<span class="org-variable-name">user</span>} = { <span class="org-string">"username"</span> =&gt; $<span class="org-variable-name">user</span>,
-                        <span class="org-string">"status"</span> =&gt; <span class="org-string">"current"</span>,
+  $<span class="org-variable-name">members</span>-&gt;{$<span class="org-variable-name">user</span>} = { <span class="org-string">"status"</span> =&gt; <span class="org-string">"current"</span>,
                         <span class="org-string">"password_front"</span> =&gt; $<span class="org-variable-name">front</span>,
                         <span class="org-string">"password_core"</span> =&gt; $<span class="org-variable-name">core</span>,
                         <span class="org-string">"password_fetchmail"</span> =&gt; $<span class="org-variable-name">vault</span> };
-  write_members_yaml
-    { <span class="org-string">"members"</span> =&gt; $<span class="org-variable-name">members</span>,
-      <span class="org-string">"revoked"</span> =&gt; $<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"revoked"</span>} };
+  write_members_yaml $<span class="org-variable-name">yaml</span>;
   mysystem (<span class="org-string">"ansible-playbook -e \@Secret/become.yml"</span>,
              <span class="org-string">" -t accounts -l core,front playbooks/site.yml"</span>);
   <span class="org-keyword">exit</span>;
@@ -7262,8 +7078,8 @@ initial, generated password.
 </div>
 </div>
 </div>
-<div id="outline-container-org5e1c62d" class="outline-3">
-<h3 id="org5e1c62d"><span class="section-number-3">12.8.</span> The Pass Command</h3>
+<div id="outline-container-org25a613b" class="outline-3">
+<h3 id="org25a613b"><span class="section-number-3">12.8.</span> The Pass Command</h3>
 <div class="outline-text-3" id="text-12-8">
 <p>
 The institute's <code>passwd</code> command on Core securely emails <code>root</code> with a
@@ -7277,8 +7093,8 @@ Ansible <a href="playbooks/site.yml"><q>site.yml</q></a> playbook to update the
 message is sent to <code>member@core</code>.
 </p>
 </div>
-<div id="outline-container-orgdf2b1f0" class="outline-4">
-<h4 id="orgdf2b1f0"><span class="section-number-4">12.8.1.</span> Less Aggressive passwd.</h4>
+<div id="outline-container-orgbd3ef07" class="outline-4">
+<h4 id="orgbd3ef07"><span class="section-number-4">12.8.1.</span> Less Aggressive passwd.</h4>
 <div class="outline-text-4" id="text-12-8-1">
 <p>
 The next code block implements the less aggressive <code>passwd</code> command.
@@ -7374,8 +7190,8 @@ print <span class="org-string">"</span>
 </div>
 </div>
 </div>
-<div id="outline-container-org02d38d2" class="outline-4">
-<h4 id="org02d38d2"><span class="section-number-4">12.8.2.</span> Less Aggressive Pass Command</h4>
+<div id="outline-container-orgf5b491e" class="outline-4">
+<h4 id="orgf5b491e"><span class="section-number-4">12.8.2.</span> Less Aggressive Pass Command</h4>
 <div class="outline-text-4" id="text-12-8-2">
 <p>
 The following code block implements the <code>./inst pass</code> command, used by
@@ -7472,8 +7288,8 @@ users:resetpassword</code> command using <code>expect(1)</code>.
 </div>
 </div>
 </div>
-<div id="outline-container-org77d62a8" class="outline-4">
-<h4 id="org77d62a8"><span class="section-number-4">12.8.3.</span> Installing the Less Aggressive passwd</h4>
+<div id="outline-container-org597c350" class="outline-4">
+<h4 id="org597c350"><span class="section-number-4">12.8.3.</span> Installing the Less Aggressive passwd</h4>
 <div class="outline-text-4" id="text-12-8-3">
 <p>
 The following Ansible tasks install the less aggressive <code>passwd</code>
@@ -7541,11 +7357,11 @@ configuration so that the email to root can be encrypted.
 </div>
 </div>
 </div>
-<div id="outline-container-org2db4274" class="outline-3">
-<h3 id="org2db4274"><span class="section-number-3">12.9.</span> The Old Command</h3>
+<div id="outline-container-org1f39111" class="outline-3">
+<h3 id="org1f39111"><span class="section-number-3">12.9.</span> The Old Command</h3>
 <div class="outline-text-3" id="text-12-9">
 <p>
-The <code>old</code> command disables a member's accounts and clients.
+The <code>old</code> command disables a member's account (and thus their clients).
 </p>
 
 <div class="org-src-container">
@@ -7560,9 +7376,7 @@ The <code>old</code> command disables a member's accounts and clients.
   mysystem (<span class="org-string">"ansible-playbook -e \@Secret/become.yml"</span>,
             <span class="org-string">"playbooks/nextcloud-old.yml -e user=$user"</span>);
   $<span class="org-variable-name">member</span>-&gt;{<span class="org-string">"status"</span>} = <span class="org-string">"former"</span>;
-  write_members_yaml { <span class="org-string">"members"</span> =&gt; $<span class="org-variable-name">members</span>,
-                       <span class="org-string">"revoked"</span> =&gt; [ sort @{$<span class="org-variable-name">member</span>-&gt;{<span class="org-string">"clients"</span>}},
-                                           @{$<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"revoked"</span>}} ] };
+  write_members_yaml $<span class="org-variable-name">yaml</span>;
   mysystem (<span class="org-string">"ansible-playbook -e \@Secret/become.yml"</span>,
             <span class="org-string">"-t accounts playbooks/site.yml"</span>);
   <span class="org-keyword">exit</span>;
@@ -7587,174 +7401,246 @@ The <code>old</code> command disables a member's accounts and clients.
 </div>
 </div>
 </div>
-<div id="outline-container-orgdc36b90" class="outline-3">
-<h3 id="orgdc36b90"><span class="section-number-3">12.10.</span> The Client Command</h3>
+<div id="outline-container-org69b4800" class="outline-3">
+<h3 id="org69b4800"><span class="section-number-3">12.10.</span> The Client Command</h3>
 <div class="outline-text-3" id="text-12-10">
 <p>
-The <code>client</code> command creates an OpenVPN configuration (<q>.ovpn</q>) file
-authorizing wireless devices to connect to the institute's VPNs.  The
-command uses the EasyRSA CA in <a href="Secret/"><q>Secret/</q></a>.  The generated configuration
-is slightly different depending on the type of host, given as the
-first argument to the command.
+The <code>client</code> command registers the public key of a client wishing to
+connect to the institute's WireGuard™ subnets.  The command allocates
+a host number, associates it with the provided public key, and updates
+the configuration files <q>front-wg0.conf</q> and <q>gate-wg0.conf</q>.  These
+are distributed to the servers, which are then reset.  Thereafter the
+servers recognize the new peer (and drop packets from any "peer" that
+is no longer authorized).
 </p>
 
-<ul class="org-ul">
-<li><code>./inst client android NEW USER</code> <br />
-An <code>android</code> host runs OpenVPN for Android or work-alike.  Two files
-are generated.  <q>campus.ovpn</q> configures a campus VPN connection,
-and <q>public.ovpn</q> configures a connection to the institute's public
-VPN.</li>
-
-<li><code>./inst client debian NEW USER</code> <br />
-A <code>debian</code> host runs a Debian desktop with Network Manager.  Again
-two files are generated, for the campus and public VPNs.</li>
-
-<li><code>./inst client campus NEW</code> <br />
-A <code>campus</code> host is a Debian host (with or without desktop) that is
-used by the institute generally, is <i>not</i> the property of a member,
-never roams off campus, and so is remotely administered with
-Ansible.  One file is generated, <q>campus.ovpn</q>.</li>
-</ul>
-
 <p>
-The administrator uses encrypted email to send <q>.ovpn</q> files to new
-members.  New members install the <code>network-manager-openvpn-gnome</code> and
-<code>openvpn-systemd-resolved</code> packages, and import the <q>.ovpn</q> files into
-Network Manager on their desktops.  The <q>.ovpn</q> files for an
-Android device are transferred by USB stick and should automatically
-install when "opened".  On campus hosts, the system administrator
-copies the <q>campus.ovpn</q> file to <q>/etc/openvpn/campus.conf</q>.
+The <code>client</code> command also generates template WireGuard™ configuration
+files for the client.  They contain the necessary parameters <i>except</i>
+the client's <code>PrivateKey</code>, which in most cases should be found in the
+local <q>/etc/wireguard/private-key</q>, <i>not</i> in the configuration files.
+Private keys (and corresponding public keys) should be generated on
+the client (i.e. by the WireGuard for Android™ app) and never revealed
+(i.e. sent in email, copied to a network drive, etc.).
 </p>
 
 <p>
-The OpenVPN configurations generated for Debian hosts specify an <code>up</code>
-script, <code>update-systemd-resolved</code>, installed in <q>/etc/openvpn/</q> by the
-<code>openvpn-systemd-resolved</code> package.  The following configuration lines
-instruct the OpenVPN clients to run this script whenever the
-connection is restarted.
+The generated configuration vary depending on the type of client,
+which must be given as the first argument to the command.  For most
+types, two configuration files are generated.  <q>campus.conf</q> contains
+the client's campus VPN configuration, and <q>public.conf</q> the client's
+public VPN configuration.
 </p>
 
-<div class="org-src-container">
-<code>openvpn-up</code><pre class="src src-conf" id="org87ee394"><code>script-security 2
-up /etc/openvpn/update-systemd-resolved
-up-restart
-</code></pre>
-</div>
+<ul class="org-ul">
+<li><code>./inst client android NAME USER PUBKEY</code> <br />
+An <code>android</code> client runs WireGuard for Android™ or work-alike.</li>
+
+<li><code>./inst client debian NAME USER PUBKEY</code> <br />
+A <code>debian</code> client runs a Debian/Linux desktop with Network Manager
+(though <code>wg-quick</code> is currently used).</li>
+
+<li><code>./inst client campus NAME PUBKEY</code> <br />
+A <code>campus</code> client is an institute machine (with or without desktop)
+that is used by the institute generally, is <i>not</i> the property of a
+member, never roams off campus, and so is remotely administered with
+Ansible.  Just one configuration file is generated: <q>campus.conf</q>.</li>
+</ul>
+
+<p>
+The administrator emails the template <q>.conf</q> files to new members.
+(They contain no secrets.)  The members will have already installed
+the <code>wireguard</code> package in order to run the <code>wg genkey</code> and <code>wg
+pubkey</code> commands.  After receiving the <q>.conf</q> templates, they paste
+in their private keys and install the resulting files in
+e.g. <q>/etc/wireguard/wg0.conf</q> and <q>wg1.conf</q>.  To connect, members
+run a command like <code>systemctl start wg-quick@wg0</code>.  (There may be
+better support in Network Manager soon.)
+</p>
 
 <div class="org-src-container">
-<a href="inst"><q>inst</q></a><pre class="src src-perl"><code><span class="org-keyword">sub</span> <span class="org-function-name">write_template</span> ($$$$$$$$$);
-<span class="org-keyword">sub</span> <span class="org-function-name">read_file</span> ($);
-<span class="org-keyword">sub</span> <span class="org-function-name">add_client</span> ($$$);
+<a href="inst"><q>inst</q></a><pre class="src src-perl"><code><span class="org-keyword">sub</span> <span class="org-function-name">write_wg_server</span> ($$$$$);
+<span class="org-keyword">sub</span> <span class="org-function-name">write_wg_client</span> ($$$$$$);
+<span class="org-keyword">sub</span> <span class="org-function-name">hostnum_to_ipaddr</span> ($$);
+<span class="org-keyword">sub</span> <span class="org-function-name">hostnum_to_ipaddr_cidr</span> ($$);
 
 <span class="org-keyword">if</span> (defined $<span class="org-variable-name">ARGV</span>[0] &amp;&amp; $<span class="org-variable-name">ARGV</span>[0] eq <span class="org-string">"client"</span>) {
-  <span class="org-keyword">die</span> <span class="org-string">"Secret/CA/easyrsa: not found\n"</span> <span class="org-keyword">if</span> ! -x <span class="org-string">"Secret/CA/easyrsa"</span>;
   <span class="org-keyword">my</span> $<span class="org-variable-name">type</span> = $<span class="org-variable-name">ARGV</span>[1]||<span class="org-string">""</span>;
   <span class="org-keyword">my</span> $<span class="org-variable-name">name</span> = $<span class="org-variable-name">ARGV</span>[2]||<span class="org-string">""</span>;
   <span class="org-keyword">my</span> $<span class="org-variable-name">user</span> = $<span class="org-variable-name">ARGV</span>[3]||<span class="org-string">""</span>;
-  <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"campus"</span>) {
-    <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client campus NAME\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 3;
+  <span class="org-keyword">my</span> $<span class="org-variable-name">pubkey</span> = $<span class="org-variable-name">ARGV</span>[4]||<span class="org-string">""</span>;
+  <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span> || $<span class="org-variable-name">type</span> eq <span class="org-string">"debian"</span>) {
+    <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client $type NAME USER PUBKEY\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 5;
     <span class="org-keyword">die</span> <span class="org-string">"$name: invalid host name\n"</span> <span class="org-keyword">if</span> $<span class="org-variable-name">name</span> !~ <span class="org-string">/^[a-z][-a-z0-9]+$/</span>;
-  } <span class="org-keyword">elsif</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span> || $<span class="org-variable-name">type</span> eq <span class="org-string">"debian"</span>) {
-    <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client $type NAME USER\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 4;
+  } <span class="org-keyword">elsif</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"campus"</span>) {
+    <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client campus NAME PUBKEY\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 4;
     <span class="org-keyword">die</span> <span class="org-string">"$name: invalid host name\n"</span> <span class="org-keyword">if</span> $<span class="org-variable-name">name</span> !~ <span class="org-string">/^[a-z][-a-z0-9]+$/</span>;
+    $<span class="org-variable-name">pubkey</span> = $<span class="org-variable-name">user</span>;
+    $<span class="org-variable-name">user</span> = <span class="org-string">""</span>;
   } <span class="org-keyword">else</span> {
-    <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client [debian|android|campus]\n"</span> <span class="org-keyword">if</span> @<span class="org-perl-non-scalar-variable">ARGV</span> != 4;
+    <span class="org-keyword">die</span> <span class="org-string">"usage: $0 client [debian|android|campus]\n"</span>;
   }
   <span class="org-keyword">my</span> $<span class="org-variable-name">yaml</span>;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">member</span>;
-  <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>) {
-    $<span class="org-variable-name">yaml</span> = read_members_yaml;
-    <span class="org-keyword">my</span> $<span class="org-variable-name">members</span> = $<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"members"</span>};
-    <span class="org-keyword">if</span> (@<span class="org-perl-non-scalar-variable">ARGV</span> == 4) {
-      $<span class="org-variable-name">member</span> = $<span class="org-variable-name">members</span>-&gt;{$<span class="org-variable-name">user</span>};
-      <span class="org-keyword">die</span> <span class="org-string">"$user: does not exist\n"</span> <span class="org-keyword">if</span> ! defined $<span class="org-variable-name">member</span>;
-    }
-    <span class="org-keyword">if</span> (defined $<span class="org-variable-name">member</span>) {
-      <span class="org-keyword">my</span> ($<span class="org-variable-name">owner</span>) = grep { grep { $<span class="org-variable-name">_</span> eq $<span class="org-variable-name">name</span> } @{$<span class="org-variable-name">_</span>-&gt;{<span class="org-string">"clients"</span>}} }
-                    values %{$<span class="org-variable-name">members</span>};
-      <span class="org-keyword">die</span> <span class="org-string">"$name: owned by $owner-&gt;{username}\n"</span>
-        <span class="org-keyword">if</span> defined $<span class="org-variable-name">owner</span> &amp;&amp; $<span class="org-variable-name">owner</span>-&gt;{username} ne $<span class="org-variable-name">member</span>-&gt;{username};
-    }
-  }
+  $<span class="org-variable-name">yaml</span> = read_members_yaml;
+  <span class="org-keyword">my</span> $<span class="org-variable-name">members</span> = $<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"members"</span>};
+  <span class="org-keyword">my</span> $<span class="org-variable-name">member</span> = $<span class="org-variable-name">members</span>-&gt;{$<span class="org-variable-name">user</span>};
+  <span class="org-keyword">die</span> <span class="org-string">"$user: does not exist\n"</span>
+    <span class="org-keyword">if</span> !defined $<span class="org-variable-name">member</span> &amp;&amp; $<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>;
 
-  <span class="org-keyword">die</span> <span class="org-string">"Secret/CA: no certificate authority found"</span>
-    <span class="org-keyword">if</span> ! -d <span class="org-string">"Secret/CA/pki/issued"</span>;
+  <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">campus_peers</span> <span class="org-comment"># [ name, hostnum, type, pubkey, user|"" ]</span>
+     = map { [ (split <span class="org-string">/ /</span>), <span class="org-string">""</span> ] } @{$<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"clients"</span>}};
 
-  <span class="org-keyword">if</span> (! -f <span class="org-string">"Secret/CA/pki/issued/$name.crt"</span>) {
-    mysystem <span class="org-string">"cd Secret/CA; ./easyrsa build-client-full $name nopass"</span>;
-  } <span class="org-keyword">else</span> {
-    print <span class="org-string">"Using existing key/cert...\n"</span>;
+  <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">member_peers</span> = ();
+  <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">u</span> (sort keys %$<span class="org-variable-name">members</span>) {
+    push @<span class="org-perl-non-scalar-variable">member_peers</span>,
+         map { [ (split <span class="org-string">/ /</span>), $<span class="org-variable-name">u</span> ] } @{$<span class="org-variable-name">members</span>-&gt;{$<span class="org-variable-name">u</span>}-&gt;{<span class="org-string">"clients"</span>}};
   }
 
-  <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>) {
-    <span class="org-keyword">my</span> $<span class="org-variable-name">clients</span> = $<span class="org-variable-name">member</span>-&gt;{<span class="org-string">"clients"</span>};
-    <span class="org-keyword">if</span> (! grep { $<span class="org-variable-name">_</span> eq $<span class="org-variable-name">name</span> } @$<span class="org-variable-name">clients</span>) {
-      $<span class="org-variable-name">member</span>-&gt;{<span class="org-string">"clients"</span>} = [ $<span class="org-variable-name">name</span>, @$<span class="org-variable-name">clients</span> ];
-      write_members_yaml $<span class="org-variable-name">yaml</span>;
-    }
+  <span class="org-keyword">my</span> @<span class="org-perl-non-scalar-variable">all_peers</span> = sort { $<span class="org-variable-name">a</span>-&gt;[1] &lt;=&gt; $<span class="org-variable-name">b</span>-&gt;[1] }
+                       (@<span class="org-perl-non-scalar-variable">campus_peers</span>, @<span class="org-perl-non-scalar-variable">member_peers</span>);
+
+  <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">p</span> (@<span class="org-perl-non-scalar-variable">all_peers</span>) {
+    <span class="org-keyword">my</span> ($<span class="org-variable-name">n</span>, $<span class="org-variable-name">h</span>, $<span class="org-variable-name">t</span>, $<span class="org-variable-name">k</span>, $<span class="org-variable-name">u</span>) = @$<span class="org-variable-name">p</span>;
+    <span class="org-keyword">die</span> <span class="org-string">"$n: name already in use by $u\n"</span>
+        <span class="org-keyword">if</span> $<span class="org-variable-name">name</span> eq $<span class="org-variable-name">n</span> &amp;&amp; $<span class="org-variable-name">u</span> ne <span class="org-string">""</span>;
+    <span class="org-keyword">die</span> <span class="org-string">"$n: name already in use on campus\n"</span>
+        <span class="org-keyword">if</span> $<span class="org-variable-name">name</span> eq $<span class="org-variable-name">n</span> &amp;&amp; $<span class="org-variable-name">u</span> eq <span class="org-string">""</span>;
   }
 
+  <span class="org-keyword">my</span> $<span class="org-variable-name">hostnum</span> = (@<span class="org-perl-non-scalar-variable">all_peers</span>
+                 ? 1 + $<span class="org-variable-name">all_peers</span>[$#<span class="org-perl-non-scalar-variable">all_peers</span>][1]
+                 : 3);
+
+  push @{$<span class="org-variable-name">type</span> eq <span class="org-string">"campus"</span>
+         ? $<span class="org-variable-name">yaml</span>-&gt;{<span class="org-string">"clients"</span>}
+         : $<span class="org-variable-name">member</span>-&gt;{<span class="org-string">"clients"</span>}},
+       <span class="org-string">"$name $hostnum $type $pubkey"</span>;
+
   umask 077;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">DEV</span> = $<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span> ? <span class="org-string">"tun"</span> : <span class="org-string">"ovpn"</span>;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">CA</span> = read_file <span class="org-string">"Secret/CA/pki/ca.crt"</span>;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">CRT</span> = read_file <span class="org-string">"Secret/CA/pki/issued/$name.crt"</span>;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">KEY</span> = read_file <span class="org-string">"Secret/CA/pki/private/$name.key"</span>;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">UP</span> = $<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span> ? <span class="org-string">""</span> : <span class="org-string">"</span>
-<span class="org-string">&lt;&lt;openvpn-up&gt;&gt;"</span>;
-
-  <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>) {
-    <span class="org-keyword">my</span> $<span class="org-variable-name">TC</span> = read_file <span class="org-string">"Secret/front-shared.key"</span>;
-    write_template ($<span class="org-variable-name">DEV</span>,$<span class="org-variable-name">UP</span>,$<span class="org-variable-name">CA</span>,$<span class="org-variable-name">CRT</span>,$<span class="org-variable-name">KEY</span>,$<span class="org-variable-name">TC</span>, $<span class="org-variable-name">front_addr</span>,
-                    $<span class="org-variable-name">domain_name</span>, <span class="org-string">"public.ovpn"</span>);
-    print <span class="org-string">"Wrote public VPN configuration to public.ovpn.\n"</span>;
+  write_members_yaml $<span class="org-variable-name">yaml</span>;
+
+  <span class="org-keyword">if</span> ($<span class="org-variable-name">type</span> eq <span class="org-string">"campus"</span>) {
+    push @<span class="org-perl-non-scalar-variable">all_peers</span>, [ $<span class="org-variable-name">name</span>, $<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">type</span>, $<span class="org-variable-name">pubkey</span>, <span class="org-string">""</span> ];
+  } <span class="org-keyword">else</span> {
+    push @<span class="org-perl-non-scalar-variable">member_peers</span>, [ $<span class="org-variable-name">name</span>, $<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">type</span>, $<span class="org-variable-name">pubkey</span>, $<span class="org-variable-name">user</span> ];
+    push @<span class="org-perl-non-scalar-variable">all_peers</span>, [ $<span class="org-variable-name">name</span>, $<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">type</span>, $<span class="org-variable-name">pubkey</span>, $<span class="org-variable-name">user</span> ];
   }
-  <span class="org-keyword">my</span> $<span class="org-variable-name">TC</span> = read_file <span class="org-string">"Secret/gate-shared.key"</span>;
-  write_template ($<span class="org-variable-name">DEV</span>,$<span class="org-variable-name">UP</span>,$<span class="org-variable-name">CA</span>,$<span class="org-variable-name">CRT</span>,$<span class="org-variable-name">KEY</span>,$<span class="org-variable-name">TC</span>, $<span class="org-variable-name">gate_wild_addr</span>,
-                  <span class="org-string">"gate.$domain_priv"</span>, <span class="org-string">"campus.ovpn"</span>);
-  print <span class="org-string">"Wrote campus VPN configuration to campus.ovpn.\n"</span>;
 
-  <span class="org-keyword">exit</span>;
+  <span class="org-keyword">my</span> $<span class="org-variable-name">core_wg_addr</span> = hostnum_to_ipaddr (2, $<span class="org-variable-name">public_wg_net_cidr</span>);
+  <span class="org-keyword">my</span> $<span class="org-variable-name">extra_front_config</span> = <span class="org-string">"</span>
+<span class="org-string">PostUp = resolvectl dns %i $core_addr</span>
+<span class="org-string">PostUp = resolvectl domain %i $domain_priv</span>
+
+<span class="org-string"># Core</span>
+<span class="org-string">[Peer]</span>
+<span class="org-string">PublicKey = $core_wg_pubkey</span>
+<span class="org-string">AllowedIPs = $core_wg_addr</span>
+<span class="org-string">AllowedIPs = $private_net_cidr</span>
+<span class="org-string">AllowedIPs = $campus_wg_net_cidr\n"</span>;
+
+  write_wg_server (<span class="org-string">"private/front-wg0.conf"</span>, \@<span class="org-perl-non-scalar-variable">member_peers</span>,
+                   hostnum_to_ipaddr_cidr (1, $<span class="org-variable-name">public_wg_net_cidr</span>),
+                   $<span class="org-variable-name">public_wg_port</span>, $<span class="org-variable-name">extra_front_config</span>)
+    <span class="org-keyword">if</span> $<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>;
+  write_wg_server (<span class="org-string">"private/gate-wg0.conf"</span>, \@<span class="org-perl-non-scalar-variable">all_peers</span>,
+                   hostnum_to_ipaddr_cidr (1, $<span class="org-variable-name">campus_wg_net_cidr</span>),
+                   $<span class="org-variable-name">campus_wg_port</span>, <span class="org-string">"\n"</span>);
+
+  write_wg_client (<span class="org-string">"public.conf"</span>,
+                   hostnum_to_ipaddr ($<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">public_wg_net_cidr</span>),
+                   $<span class="org-variable-name">type</span>,
+                   $<span class="org-variable-name">front_wg_pubkey</span>,
+                   <span class="org-string">"$front_addr:$public_wg_port"</span>,
+                   hostnum_to_ipaddr (1, $<span class="org-variable-name">public_wg_net_cidr</span>))
+    <span class="org-keyword">if</span> $<span class="org-variable-name">type</span> ne <span class="org-string">"campus"</span>;
+  write_wg_client (<span class="org-string">"campus.conf"</span>,
+                   hostnum_to_ipaddr ($<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">campus_wg_net_cidr</span>),
+                   $<span class="org-variable-name">type</span>,
+                   $<span class="org-variable-name">gate_wg_pubkey</span>,
+                   <span class="org-string">"$gate_wild_addr:$campus_wg_port"</span>,
+                   hostnum_to_ipaddr (1, $<span class="org-variable-name">campus_wg_net_cidr</span>));
 }
 
-<span class="org-keyword">sub</span> <span class="org-function-name">write_template</span> ($$$$$$$$$) {
-  <span class="org-keyword">my</span> ($<span class="org-variable-name">DEV</span>,$<span class="org-variable-name">UP</span>,$<span class="org-variable-name">CA</span>,$<span class="org-variable-name">CRT</span>,$<span class="org-variable-name">KEY</span>,$<span class="org-variable-name">TC</span>,$<span class="org-variable-name">ADDR</span>,$<span class="org-variable-name">NAME</span>,$<span class="org-variable-name">FILE</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
+<span class="org-keyword">sub</span> <span class="org-function-name">write_wg_server</span> ($$$$$) {
+  <span class="org-keyword">my</span> ($<span class="org-variable-name">file</span>, $<span class="org-variable-name">peers</span>, $<span class="org-variable-name">addr_cidr</span>, $<span class="org-variable-name">port</span>, $<span class="org-variable-name">extra</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
   <span class="org-keyword">my</span> $<span class="org-variable-name">O</span> = new IO::File;
-  open ($<span class="org-variable-name">O</span>, <span class="org-string">"&gt;$FILE.tmp"</span>) or <span class="org-keyword">die</span> <span class="org-string">"Could not open $FILE.tmp: $!\n"</span>;
-  print $<span class="org-variable-name">O</span> <span class="org-string">"client</span>
-<span class="org-string">dev-type tun</span>
-<span class="org-string">dev $DEV</span>
-<span class="org-string">remote $ADDR</span>
-<span class="org-string">nobind</span>
-<span class="org-string">&lt;&lt;openvpn-drop-priv&gt;&gt;</span>
-<span class="org-string">remote-cert-tls server</span>
-<span class="org-string">verify-x509-name $NAME name</span>
-<span class="org-string">&lt;&lt;openvpn-crypt&gt;&gt;$UP</span>
-<span class="org-string">verb 3</span>
-<span class="org-string">key-direction 1</span>
-<span class="org-string">&lt;ca&gt;\n$CA&lt;/ca&gt;</span>
-<span class="org-string">&lt;cert&gt;\n$CRT&lt;/cert&gt;</span>
-<span class="org-string">&lt;key&gt;\n$KEY&lt;/key&gt;</span>
-<span class="org-string">&lt;tls-crypt&gt;\n$TC&lt;/tls-crypt&gt;\n"</span>;
-  close $<span class="org-variable-name">O</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not close $FILE.tmp: $!\n"</span>;
-  rename (<span class="org-string">"$FILE.tmp"</span>, $<span class="org-variable-name">FILE</span>)
-    or <span class="org-keyword">die</span> <span class="org-string">"Could not rename $FILE.tmp: $!\n"</span>;
+  open ($<span class="org-variable-name">O</span>, <span class="org-string">"&gt;$file.tmp"</span>) or <span class="org-keyword">die</span> <span class="org-string">"Could not open $file.tmp: $!\n"</span>;
+  print $<span class="org-variable-name">O</span> <span class="org-string">"[Interface]</span>
+<span class="org-string">Address = $addr_cidr</span>
+<span class="org-string">ListenPort = $port</span>
+<span class="org-string">PostUp = wg set %i private-key /etc/wireguard/private-key$extra"</span>;
+  <span class="org-keyword">for</span> <span class="org-keyword">my</span> $<span class="org-variable-name">p</span> (@$<span class="org-variable-name">peers</span>) {
+    <span class="org-keyword">my</span> ($<span class="org-variable-name">n</span>, $<span class="org-variable-name">h</span>, $<span class="org-variable-name">t</span>, $<span class="org-variable-name">k</span>, $<span class="org-variable-name">u</span>) = @$<span class="org-variable-name">p</span>;
+    <span class="org-keyword">next</span> <span class="org-keyword">if</span> $<span class="org-variable-name">k</span> =~ <span class="org-string">/^-/</span>;
+    <span class="org-keyword">my</span> $<span class="org-variable-name">ip</span> = hostnum_to_ipaddr ($<span class="org-variable-name">h</span>, $<span class="org-variable-name">addr_cidr</span>);
+    print $<span class="org-variable-name">O</span> <span class="org-string">"</span>
+<span class="org-string"># $n</span>
+<span class="org-string">[Peer]</span>
+<span class="org-string">PublicKey = $k</span>
+<span class="org-string">AllowedIPs = $ip\n"</span>;
+  }
+  close $<span class="org-variable-name">O</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not close $file.tmp: $!\n"</span>;
+  rename (<span class="org-string">"$file.tmp"</span>, $<span class="org-variable-name">file</span>)
+    or <span class="org-keyword">die</span> <span class="org-string">"Could not rename $file.tmp: $!\n"</span>;
 }
 
-<span class="org-keyword">sub</span> <span class="org-function-name">read_file</span> ($) {
-  <span class="org-keyword">my</span> ($<span class="org-variable-name">path</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">I</span> = new IO::File;
-  open ($<span class="org-variable-name">I</span>, <span class="org-string">"&lt;$path"</span>) or <span class="org-keyword">die</span> <span class="org-string">"$path: could not read: $!\n"</span>;
-  <span class="org-keyword">local</span> $/;
-  <span class="org-keyword">my</span> $<span class="org-variable-name">c</span> = &lt;$<span class="org-variable-name">I</span>&gt;;
-  close $<span class="org-variable-name">I</span> or <span class="org-keyword">die</span> <span class="org-string">"$path: could not close: $!\n"</span>;
-  <span class="org-keyword">return</span> $<span class="org-variable-name">c</span>;
+<span class="org-keyword">sub</span> <span class="org-function-name">write_wg_client</span> ($$$$$$) {
+  <span class="org-keyword">my</span> ($<span class="org-variable-name">file</span>, $<span class="org-variable-name">addr</span>, $<span class="org-variable-name">type</span>, $<span class="org-variable-name">pubkey</span>, $<span class="org-variable-name">endpt</span>, $<span class="org-variable-name">server_addr</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
+  <span class="org-keyword">my</span> $<span class="org-variable-name">O</span> = new IO::File;
+  <span class="org-keyword">my</span> $<span class="org-variable-name">DNS</span> = ($<span class="org-variable-name">type</span> eq <span class="org-string">"android"</span>
+             ? <span class="org-string">"</span>
+<span class="org-string">DNS=$core_addr\nDomain=$domain_priv"</span>
+             : <span class="org-string">"</span>
+<span class="org-string">PostUp = resolvectl dns %i $core_addr</span>
+<span class="org-string">PostUp = resolvectl domain %i $domain_priv"</span>);
+  open ($<span class="org-variable-name">O</span>, <span class="org-string">"&gt;$file.tmp"</span>) or <span class="org-keyword">die</span> <span class="org-string">"Could not open $file.tmp: $!\n"</span>;
+  print $<span class="org-variable-name">O</span> <span class="org-string">"[Interface]</span>
+<span class="org-string">Address = $addr</span>
+<span class="org-string">PostUp = wg set %i private-key /etc/wireguard/private-key$DNS</span>
+
+<span class="org-string">[Peer]</span>
+<span class="org-string">PublicKey = $pubkey</span>
+<span class="org-string">EndPoint = $endpt</span>
+<span class="org-string">AllowedIPs = $server_addr</span>
+<span class="org-string">AllowedIPs = $private_net_cidr</span>
+<span class="org-string">AllowedIPs = $public_wg_net_cidr</span>
+<span class="org-string">AllowedIPs = $campus_wg_net_cidr\n"</span>;
+  close $<span class="org-variable-name">O</span> or <span class="org-keyword">die</span> <span class="org-string">"Could not close $file.tmp: $!\n"</span>;
+  rename (<span class="org-string">"$file.tmp"</span>, $<span class="org-variable-name">file</span>)
+    or <span class="org-keyword">die</span> <span class="org-string">"Could not rename $file.tmp: $!\n"</span>;
+
+  <span class="org-keyword">exit</span>;
 }
+
+<span class="org-keyword">sub</span> <span class="org-function-name">hostnum_to_ipaddr</span> ($$)
+{
+  <span class="org-keyword">my</span> ($<span class="org-variable-name">hostnum</span>, $<span class="org-variable-name">net_cidr</span>) = @<span class="org-perl-non-scalar-variable">_</span>;
+
+  <span class="org-comment"># Assume 24bit subnet, 8bit hostnum.</span>
+  <span class="org-comment"># Find a Perl library for more generality?</span>
+  <span class="org-keyword">die</span> <span class="org-string">"$hostnum: hostnum too large\n"</span> <span class="org-keyword">if</span> $<span class="org-variable-name">hostnum</span> &gt; 255;
+  <span class="org-keyword">my</span> ($<span class="org-variable-name">prefix</span>) = $<span class="org-variable-name">net_cidr</span> =~ m<span class="org-string">"^(\d+\.\d+\.\d+)\.\d+/24$";</span>
+<span class="org-string">  die if !$prefix;</span>
+<span class="org-string">  return "</span>$<span class="org-variable-name">prefix</span>.$<span class="org-variable-name">hostnum</span><span class="org-string">";</span>
+<span class="org-string">}</span>
+
+<span class="org-string">sub hostnum_to_ipaddr_cidr ($$)</span>
+<span class="org-string">{</span>
+<span class="org-string">  my ($hostnum, $net_cidr) = @_;</span>
+
+<span class="org-string">  # Assume 24bit subnet, 8bit hostnum.</span>
+<span class="org-string">  # Find a Perl library for more generality?</span>
+<span class="org-string">  die "</span>$<span class="org-variable-name">hostnum</span>: hostnum too large\n<span class="org-string">" if $hostnum &gt; 255;</span>
+<span class="org-string">  my ($prefix) = $net_cidr =~ m"</span>^(\d+\.\d+\.\d+)\.\d+<span class="org-string">/24$";</span>
+<span class="org-string">  die if !$prefix;</span>
+<span class="org-string">  return "$prefix.$hostnum/</span>24<span class="org-string">";</span>
+<span class="org-string">}</span>
 </code></pre>
 </div>
 </div>
 </div>
-<div id="outline-container-orgb98a15e" class="outline-3">
-<h3 id="orgb98a15e"><span class="section-number-3">12.11.</span> Institute Command Help</h3>
+<div id="outline-container-org01141e3" class="outline-3">
+<h3 id="org01141e3"><span class="section-number-3">12.11.</span> Institute Command Help</h3>
 <div class="outline-text-3" id="text-12-11">
 <p>
 This should be the last block tangled into the <a href="inst"><q>inst</q></a> script.  It
@@ -7770,8 +7656,8 @@ above.
 </div>
 </div>
 </div>
-<div id="outline-container-org7832c43" class="outline-2">
-<h2 id="org7832c43"><span class="section-number-2">13.</span> Testing</h2>
+<div id="outline-container-org3d2b7a1" class="outline-2">
+<h2 id="org3d2b7a1"><span class="section-number-2">13.</span> Testing</h2>
 <div class="outline-text-2" id="text-13">
 <p>
 The example files in this document, <a href="ansible.cfg"><q>ansible.cfg</q></a> and <a href="hosts"><q>hosts</q></a> as well
@@ -7790,7 +7676,7 @@ simulation is the VirtualBox host.
 <p>
 The next two sections list the steps taken to create the simulated
 Core, Gate and Front machines, and connect them to their networks.
-The process is similar to that described in <a href="#orgbf29081">The (Actual) Hardware</a>, but
+The process is similar to that described in <a href="#org5af003d">The (Actual) Hardware</a>, but
 is covered in detail here where the VirtualBox hypervisor can be
 assumed and exact command lines can be given (and copied during
 re-testing).  The remaining sections describe the manual testing
@@ -7806,8 +7692,8 @@ HTML version of the latest revision can be found on the official web
 site at <a href="https://www.virtualbox.org/manual/UserManual.html">https://www.virtualbox.org/manual/UserManual.html</a>.
 </p>
 </div>
-<div id="outline-container-orgf8057e6" class="outline-3">
-<h3 id="orgf8057e6"><span class="section-number-3">13.1.</span> The Test Networks</h3>
+<div id="outline-container-org7da169b" class="outline-3">
+<h3 id="org7da169b"><span class="section-number-3">13.1.</span> The Test Networks</h3>
 <div class="outline-text-3" id="text-13-1">
 <p>
 The networks used in the test:
@@ -7871,15 +7757,15 @@ on the private <code>192.168.15.0/24</code> network.
 </p>
 </div>
 </div>
-<div id="outline-container-orgd7a9abd" class="outline-3">
-<h3 id="orgd7a9abd"><span class="section-number-3">13.2.</span> The Test Machines</h3>
+<div id="outline-container-org78ae748" class="outline-3">
+<h3 id="org78ae748"><span class="section-number-3">13.2.</span> The Test Machines</h3>
 <div class="outline-text-3" id="text-13-2">
 <p>
 The virtual machines are created by <code>VBoxManage</code> command lines in the
 following sub-sections.  They each start with a recent Debian release
 (e.g. <q>debian-12.5.0-amd64-netinst.iso</q>) in their simulated DVD
-drives.  As in <a href="#orgbf29081">The Hardware</a> preparation process being simulated, a few
-additional software packages are installed.  Unlike in <a href="#orgbf29081">The Hardware</a>
+drives.  As in <a href="#org5af003d">The Hardware</a> preparation process being simulated, a few
+additional software packages are installed.  Unlike in <a href="#org5af003d">The Hardware</a>
 preparation, machines are moved to their final networks and <i>then</i>
 remote access is authorized.  (They are not accessible via <code>ssh</code> on
 the VirtualBox NAT network where they first boot.)
@@ -7891,8 +7777,8 @@ privileged accounts on the virtual machines, they are prepared for
 configuration by Ansible.
 </p>
 </div>
-<div id="outline-container-org310a70b" class="outline-4">
-<h4 id="org310a70b"><span class="section-number-4">13.2.1.</span> A Test Machine</h4>
+<div id="outline-container-org02a5366" class="outline-4">
+<h4 id="org02a5366"><span class="section-number-4">13.2.1.</span> A Test Machine</h4>
 <div class="outline-text-4" id="text-13-2-1">
 <p>
 The following shell function contains most of the <code>VBoxManage</code>
@@ -8023,8 +7909,8 @@ preparation (below).
 </p>
 </div>
 </div>
-<div id="outline-container-org28db7d4" class="outline-4">
-<h4 id="org28db7d4"><span class="section-number-4">13.2.2.</span> The Test Front Machine</h4>
+<div id="outline-container-org944111f" class="outline-4">
+<h4 id="org944111f"><span class="section-number-4">13.2.2.</span> The Test Front Machine</h4>
 <div class="outline-text-4" id="text-13-2-2">
 <p>
 The <code>front</code> machine is created with 512MiB of RAM, 4GiB of disk, and
@@ -8037,7 +7923,7 @@ After Debian is installed (as detailed above) <code>front</code> is shut down an
 its primary network interface moved to the simulated Internet, the NAT
 network <code>premises</code>.  <code>front</code> also gets a second network interface, on
 the host-only network <code>vboxnet1</code>, to make it directly accessible to
-the administrator's notebook (as described in <a href="#orgf8057e6">The Test Networks</a>).
+the administrator's notebook (as described in <a href="#org7da169b">The Test Networks</a>).
 </p>
 
 <div class="org-src-container">
@@ -8069,13 +7955,13 @@ Note that there is no pre-provisioning for <code>front</code>, which is never
 deployed on a frontier, always in the cloud.  Additional Debian
 packages are assumed to be readily available.  Thus Ansible installs
 them as necessary, but first the administrator authorizes remote
-access by following the instructions in the final section: <a href="#orgab73a63">Ansible
+access by following the instructions in the final section: <a href="#org8af5300">Ansible
 Test Authorization</a>.
 </p>
 </div>
 </div>
-<div id="outline-container-org4da3a01" class="outline-4">
-<h4 id="org4da3a01"><span class="section-number-4">13.2.3.</span> The Test Gate Machine</h4>
+<div id="outline-container-org667e79f" class="outline-4">
+<h4 id="org667e79f"><span class="section-number-4">13.2.3.</span> The Test Gate Machine</h4>
 <div class="outline-text-4" id="text-13-2-3">
 <p>
 The <code>gate</code> machine is created with the same amount of RAM and disk as
@@ -8090,14 +7976,14 @@ create_vm
 </div>
 
 <p>
-After Debian is installed (as detailed in <a href="#org310a70b">A Test Machine</a>) and the
+After Debian is installed (as detailed in <a href="#org02a5366">A Test Machine</a>) and the
 machine rebooted, the administrator logs in and installs several
 additional software packages.
 </p>
 
 <div class="org-src-container">
 <pre class="src src-sh"><code>sudo apt install netplan.io systemd-resolved unattended-upgrades <span class="org-sh-escaped-newline">\</span>
-                 ufw isc-dhcp-server postfix openvpn
+                 ufw isc-dhcp-server postfix wireguard
 </code></pre>
 </div>
 
@@ -8191,12 +8077,12 @@ Ethernet interface is temporarily configured with an IP address.
 
 <p>
 Finally, the administrator authorizes remote access by following the
-instructions in the final section: <a href="#orgab73a63">Ansible Test Authorization</a>.
+instructions in the final section: <a href="#org8af5300">Ansible Test Authorization</a>.
 </p>
 </div>
 </div>
-<div id="outline-container-org1f40acc" class="outline-4">
-<h4 id="org1f40acc"><span class="section-number-4">13.2.4.</span> The Test Core Machine</h4>
+<div id="outline-container-orge993c12" class="outline-4">
+<h4 id="orge993c12"><span class="section-number-4">13.2.4.</span> The Test Core Machine</h4>
 <div class="outline-text-4" id="text-13-2-4">
 <p>
 The <code>core</code> machine is created with 1GiB of RAM and 6GiB of disk.
@@ -8213,14 +8099,14 @@ create_vm
 </div>
 
 <p>
-After Debian is installed (as detailed in <a href="#org310a70b">A Test Machine</a>) and the
+After Debian is installed (as detailed in <a href="#org02a5366">A Test Machine</a>) and the
 machine rebooted, the administrator logs in and installs several
 additional software packages.
 </p>
 
 <div class="org-src-container">
 <pre class="src src-sh"><code>sudo apt install netplan.io systemd-resolved unattended-upgrades <span class="org-sh-escaped-newline">\</span>
-                 ntp isc-dhcp-server bind9 apache2 openvpn <span class="org-sh-escaped-newline">\</span>
+                 ntp isc-dhcp-server bind9 apache2 wireguard <span class="org-sh-escaped-newline">\</span>
                  postfix dovecot-imapd fetchmail expect rsync <span class="org-sh-escaped-newline">\</span>
                  gnupg
 sudo apt install mariadb-server php php-{apcu,bcmath,curl,gd,gmp}<span class="org-sh-escaped-newline">\</span>
@@ -8279,12 +8165,12 @@ Netplan soon.)
 
 <p>
 Finally, the administrator authorizes remote access by following the
-instructions in the next section: <a href="#orgab73a63">Ansible Test Authorization</a>.
+instructions in the next section: <a href="#org8af5300">Ansible Test Authorization</a>.
 </p>
 </div>
 </div>
-<div id="outline-container-orgab73a63" class="outline-4">
-<h4 id="orgab73a63"><span class="section-number-4">13.2.5.</span> Ansible Test Authorization</h4>
+<div id="outline-container-org8af5300" class="outline-4">
+<h4 id="org8af5300"><span class="section-number-4">13.2.5.</span> Ansible Test Authorization</h4>
 <div class="outline-text-4" id="text-13-2-5">
 <p>
 To authorize Ansible's access to the three test machines, they must
@@ -8350,8 +8236,8 @@ ssh-keygen -f ~/.ssh/known_hosts -R 192.168.57.3
 </div>
 </div>
 </div>
-<div id="outline-container-org870180a" class="outline-3">
-<h3 id="org870180a"><span class="section-number-3">13.3.</span> Configure Test Machines</h3>
+<div id="outline-container-org885ec79" class="outline-3">
+<h3 id="org885ec79"><span class="section-number-3">13.3.</span> Configure Test Machines</h3>
 <div class="outline-text-3" id="text-13-3">
 <p>
 At this point the three test machines <code>core</code>, <code>gate</code>, and <code>front</code> are
@@ -8369,8 +8255,8 @@ not</i>.
 </p>
 </div>
 </div>
-<div id="outline-container-orga849e1c" class="outline-3">
-<h3 id="orga849e1c"><span class="section-number-3">13.4.</span> Test Basics</h3>
+<div id="outline-container-org340680b" class="outline-3">
+<h3 id="org340680b"><span class="section-number-3">13.4.</span> Test Basics</h3>
 <div class="outline-text-3" id="text-13-4">
 <p>
 At this point the test institute is just <code>core</code>, <code>gate</code> and <code>front</code>,
@@ -8432,12 +8318,12 @@ instant attention).
 </p>
 </div>
 </div>
-<div id="outline-container-org560f2c4" class="outline-3">
-<h3 id="org560f2c4"><span class="section-number-3">13.5.</span> The Test Nextcloud</h3>
+<div id="outline-container-org4fd6e9c" class="outline-3">
+<h3 id="org4fd6e9c"><span class="section-number-3">13.5.</span> The Test Nextcloud</h3>
 <div class="outline-text-3" id="text-13-5">
 <p>
 Further tests involve Nextcloud account management.  Nextcloud is
-installed on <code>core</code> as described in <a href="#org640e4f7">Configure Nextcloud</a>.  Once
+installed on <code>core</code> as described in <a href="#org68d4766">Configure Nextcloud</a>.  Once
 <q>/Nextcloud/</q> is created, <code>./inst config core</code> will validate
 or update its configuration files.
 </p>
@@ -8454,13 +8340,13 @@ exercise the test Nextcloud.
 
 <p>
 The process starts with enrolling the first member of the institute
-using the <code>./inst new</code> command and issuing client VPN keys with the
-<code>./inst client</code> command.
+using the <code>./inst new</code> command and registering a client's public key
+with the <code>./inst client</code> command.
 </p>
 </div>
 </div>
-<div id="outline-container-orgd08a549" class="outline-3">
-<h3 id="orgd08a549"><span class="section-number-3">13.6.</span> Test New Command</h3>
+<div id="outline-container-org3a8cb8b" class="outline-3">
+<h3 id="org3a8cb8b"><span class="section-number-3">13.6.</span> Test New Command</h3>
 <div class="outline-text-3" id="text-13-6">
 <p>
 A member must be enrolled so that a member's client machine can be
@@ -8480,14 +8366,14 @@ Take note of Dick's initial password.
 </p>
 </div>
 </div>
-<div id="outline-container-orgdab6eef" class="outline-3">
-<h3 id="orgdab6eef"><span class="section-number-3">13.7.</span> The Test Member Notebook</h3>
+<div id="outline-container-orgca38551" class="outline-3">
+<h3 id="orgca38551"><span class="section-number-3">13.7.</span> The Test Member Notebook</h3>
 <div class="outline-text-3" id="text-13-7">
 <p>
 A test member's notebook is created next, much like the servers,
 except with memory and disk space doubled to 2GiB and 8GiB, and a
 desktop.  This machine is not configured by Ansible.  Rather, its
-desktop VPN client and web browser test the OpenVPN configurations on
+WireGuard™ tunnels and web browser test the VPN configurations on
 <code>gate</code> and <code>front</code>, and the Nextcloud installation on <code>core</code>.
 </p>
 
@@ -8509,7 +8395,7 @@ behind) the access point.
 </p>
 
 <p>
-Debian is installed much as detailed in <a href="#org310a70b">A Test Machine</a> <i>except</i> that
+Debian is installed much as detailed in <a href="#org02a5366">A Test Machine</a> <i>except</i> that
 the SSH server option is <i>not</i> needed and the GNOME desktop option
 <i>is</i>.  When the machine reboots, the administrator logs into the
 desktop and installs a couple additional software packages (which
@@ -8517,61 +8403,77 @@ require several more).
 </p>
 
 <div class="org-src-container">
-<pre class="src src-nil"><code>sudo apt install network-manager-openvpn-gnome \
-                 openvpn-systemd-resolved \
-                 nextcloud-desktop evolution
+<pre class="src src-nil"><code>sudo apt install wireguard nextcloud-desktop evolution
 </code></pre>
 </div>
 </div>
 </div>
-<div id="outline-container-orgec34fb6" class="outline-3">
-<h3 id="orgec34fb6"><span class="section-number-3">13.8.</span> Test Client Command</h3>
+<div id="outline-container-org8af30e9" class="outline-3">
+<h3 id="org8af30e9"><span class="section-number-3">13.8.</span> Test Client Command</h3>
 <div class="outline-text-3" id="text-13-8">
 <p>
-The <code>./inst client</code> command is used to issue keys for the institute's
-VPNs.  The following command generates two <q>.ovpn</q> (OpenVPN
-configuration) files, <q>small.ovpn</q> and <q>campus.ovpn</q>, authorizing
-access by the holder, identified as <code>dick</code>, owned by member <code>dick</code>, to
-the test VPNs.
+The <code>./inst client</code> command is used to register the public key of a
+client wishing to connect to the institute's VPNs.  In this test, new
+member Dick wants to connect his notebook, <code>dick</code>, to the institute
+VPNs.  First he generates a pair of WireGuard™ keys by running the
+following commands on Dick's notebook.
 </p>
 
 <div class="org-src-container">
-<pre class="src src-sh"><code>./inst client debian dick dick
+<pre class="src src-sh"><code>( <span class="org-builtin">umask</span> 077; wg genkey &gt;private)
+wg pubkey &lt;private &gt;public
+</code></pre>
+</div>
+
+<p>
+The administrator uses the key in <q>public</q> to run the following
+command, generating <q>campus.conf</q> and <q>public.conf</q> files.
+</p>
+
+<div class="org-src-container">
+<pre class="src src-sh"><code>./inst client debian dick dick <span class="org-sh-escaped-newline">\</span>
+  4qd4xdRztZBKhFrX9jI/b4fnMzpKQ5qhg691hwYSsX8=
 </code></pre>
 </div>
 </div>
 </div>
-<div id="outline-container-org788a72a" class="outline-3">
-<h3 id="org788a72a"><span class="section-number-3">13.9.</span> Test Campus VPN</h3>
+<div id="outline-container-orgc52b7a2" class="outline-3">
+<h3 id="orgc52b7a2"><span class="section-number-3">13.9.</span> Test Campus WireGuard™ Subnet</h3>
 <div class="outline-text-3" id="text-13-9">
 <p>
-The <q>campus.ovpn</q> OpenVPN configuration file (generated in <a href="#orgec34fb6">Test Client
-Command</a>) is transferred to <code>dick</code>, which is at the Wi-Fi access
-point's <code>wifi_wan_addr</code>.
+The <q>campus.conf</q> WireGuard™ configuration file (generated in <a href="#org8af30e9">Test
+Client Command</a>) is transferred to <code>dick</code>, which is at the Wi-Fi access
+point's IP address, host 2 on the wild Ethernet.
 </p>
 
 <div class="org-src-container">
-<pre class="src src-sh"><code>scp *.ovpn sysadm@192.168.57.2:
+<pre class="src src-sh"><code>scp *.conf sysadm@192.168.57.2:
 </code></pre>
 </div>
 
 <p>
-The file is installed using the Network tab of the desktop Settings
-app.  The administrator uses the "+" button, chooses "Import from
-file&#x2026;" and the <q>campus.ovpn</q> file.  <i>Importantly</i> the administrator
-checks the "Use this connection only for resources on its network"
-checkbox in the IPv4 tab of the Add VPN dialog.  The admin does the
-same with the <q>small.ovpn</q> file, for use on the simulated Internet.
+Dick then pastes his notebook's private key into the template
+<q>campus.conf</q> file and installs the result in
+<q>/etc/wireguard/wg0.conf</q>, doing the same to complete <q>public.conf</q>
+and install it in <q>/etc/wireguard/wg1.conf</q>.
 </p>
 
 <p>
-The administrator turns on the campus VPN on <code>dick</code> (which connects
-instantly) and does a few basic tests in a terminal.
+To connect to the campus VPN, the following command is run.
+</p>
+
+<div class="org-src-container">
+<pre class="src src-sh"><code>systemctl start wg-quick@wg0
+</code></pre>
+</div>
+
+<p>
+A few basic tests are then performed in a terminal.
 </p>
 
 <div class="org-src-container">
 <pre class="src src-sh"><code>systemctl status
-ping -c 1 8.8.4.4      <span class="org-comment-delimiter"># </span><span class="org-comment">dns.google</span>
+ping -c 1 8.8.8.8      <span class="org-comment-delimiter"># </span><span class="org-comment">dns.google</span>
 ping -c 1 192.168.56.1 <span class="org-comment-delimiter"># </span><span class="org-comment">core</span>
 host dns.google
 host core.small.private
@@ -8580,8 +8482,8 @@ host www
 </div>
 </div>
 </div>
-<div id="outline-container-org6342178" class="outline-3">
-<h3 id="org6342178"><span class="section-number-3">13.10.</span> Test Web Pages</h3>
+<div id="outline-container-org6257673" class="outline-3">
+<h3 id="org6257673"><span class="section-number-3">13.10.</span> Test Web Pages</h3>
 <div class="outline-text-3" id="text-13-10">
 <p>
 Next, the administrator copies <a href="Backup/WWW/"><q>Backup/WWW/</q></a> (included in the
@@ -8620,8 +8522,8 @@ will warn but allow the luser to continue.
 </p>
 </div>
 </div>
-<div id="outline-container-org9931f74" class="outline-3">
-<h3 id="org9931f74"><span class="section-number-3">13.11.</span> Test Web Update</h3>
+<div id="outline-container-org2959fda" class="outline-3">
+<h3 id="org2959fda"><span class="section-number-3">13.11.</span> Test Web Update</h3>
 <div class="outline-text-3" id="text-13-11">
 <p>
 Modify <q>/WWW/live/index.html</q> on <code>core</code> and wait 15 minutes for it to
@@ -8635,8 +8537,8 @@ Hack <q>/home/www/index.html</q> on <code>front</code> and observe the result at
 </p>
 </div>
 </div>
-<div id="outline-container-org0145aad" class="outline-3">
-<h3 id="org0145aad"><span class="section-number-3">13.12.</span> Test Nextcloud</h3>
+<div id="outline-container-org99a0083" class="outline-3">
+<h3 id="org99a0083"><span class="section-number-3">13.12.</span> Test Nextcloud</h3>
 <div class="outline-text-3" id="text-13-12">
 <p>
 Nextcloud is typically installed and configured <i>after</i> the first
@@ -8644,9 +8546,9 @@ Ansible run, when <code>core</code> has Internet access via <code>gate</code>.
 installation directory <q>/Nextcloud/nextcloud/</q> appears, the Ansible
 code skips parts of the Nextcloud configuration.  The same
 installation (or restoration) process used on Core is used on <code>core</code>
-to create <q>/Nextcloud/</q>.  The process starts with <a href="#orgdb3439f">Create
-<q>/Nextcloud/</q></a>, involves <a href="#org3c8ed7b">Restore Nextcloud</a> or <a href="#orgb7a7660">Install Nextcloud</a>,
-and runs <code>./inst config core</code> again <a href="#orgdc9a64a">8.23.6</a>.  When the <code>./inst
+to create <q>/Nextcloud/</q>.  The process starts with <a href="#org7ba56a3">Create
+<q>/Nextcloud/</q></a>, involves <a href="#orgabfc848">Restore Nextcloud</a> or <a href="#org50fbfc3">Install Nextcloud</a>,
+and runs <code>./inst config core</code> again <a href="#orgb106f73">8.23.6</a>.  When the <code>./inst
 config core</code> command is happy with the Nextcloud configuration on
 <code>core</code>, the administrator uses Dick's notebook to test it, performing
 the following tests on <code>dick</code>'s desktop.
@@ -8724,8 +8626,8 @@ the calendar.</li>
 </ul>
 </div>
 </div>
-<div id="outline-container-org89efb80" class="outline-3">
-<h3 id="org89efb80"><span class="section-number-3">13.13.</span> Test Email</h3>
+<div id="outline-container-orge632edc" class="outline-3">
+<h3 id="orge632edc"><span class="section-number-3">13.13.</span> Test Email</h3>
 <div class="outline-text-3" id="text-13-13">
 <p>
 With Evolution running on the member notebook <code>dick</code>, one second email
@@ -8753,8 +8655,8 @@ Outgoing email is also tested.  A message to
 </p>
 </div>
 </div>
-<div id="outline-container-orgd3f059a" class="outline-3">
-<h3 id="orgd3f059a"><span class="section-number-3">13.14.</span> Test Public VPN</h3>
+<div id="outline-container-org6bd435a" class="outline-3">
+<h3 id="org6bd435a"><span class="section-number-3">13.14.</span> Test Public VPN</h3>
 <div class="outline-text-3" id="text-13-14">
 <p>
 At this point, <code>dick</code> can move abroad, from the campus Wi-Fi
@@ -8769,12 +8671,17 @@ machine does not need to be shut down.
 </div>
 
 <p>
-The administrator might wait to see evidence of the change in
-networks.  Evolution may start "Testing reachability of mail account
-dick@small.example.org."  Eventually, the <code>campus</code> VPN should
-disconnect.  After it does, the administrator turns on the <code>small</code>
-VPN, which connects in a second or two.  Again, some basics are
-tested in a terminal.
+Then the campus VPN is disconnected and the public VPN connected.
+</p>
+
+<div class="org-src-container">
+<pre class="src src-sh"><code>systemctl stop wg-quick@wg0
+systemctl start wg-quick@wg1
+</code></pre>
+</div>
+
+<p>
+Again, some basics are tested in a terminal.
 </p>
 
 <div class="org-src-container">
@@ -8807,8 +8714,8 @@ calendar events.
 </p>
 </div>
 </div>
-<div id="outline-container-org1fd1f8b" class="outline-3">
-<h3 id="org1fd1f8b"><span class="section-number-3">13.15.</span> Test Pass Command</h3>
+<div id="outline-container-orge9a81e6" class="outline-3">
+<h3 id="orge9a81e6"><span class="section-number-3">13.15.</span> Test Pass Command</h3>
 <div class="outline-text-3" id="text-13-15">
 <p>
 To test the <code>./inst pass</code> command, the administrator logs in to <code>core</code>
@@ -8855,8 +8762,8 @@ Finally, the administrator verifies that <code>dick</code> can login on <code>co
 </p>
 </div>
 </div>
-<div id="outline-container-org567770b" class="outline-3">
-<h3 id="org567770b"><span class="section-number-3">13.16.</span> Test Old Command</h3>
+<div id="outline-container-org2b49661" class="outline-3">
+<h3 id="org2b49661"><span class="section-number-3">13.16.</span> Test Old Command</h3>
 <div class="outline-text-3" id="text-13-16">
 <p>
 One more institute command is left to exercise.  The administrator
@@ -8870,22 +8777,22 @@ retires <code>dick</code> and his main device <code>dick</code>.
 
 <p>
 The administrator tests Dick's access to <code>core</code>, <code>front</code> and
-Nextcloud, and attempts to re-connect the <code>small</code> VPN.  All of these
-should fail.
+Nextcloud, and attempts to access the campus VPN.  All of these should
+fail.
 </p>
 </div>
 </div>
 </div>
-<div id="outline-container-orgf6f9793" class="outline-2">
-<h2 id="orgf6f9793"><span class="section-number-2">14.</span> Future Work</h2>
+<div id="outline-container-orgc795c6a" class="outline-2">
+<h2 id="orgc795c6a"><span class="section-number-2">14.</span> Future Work</h2>
 <div class="outline-text-2" id="text-14">
 <p>
 The small institute's network, as currently defined in this doocument,
 is lacking in a number of respects.
 </p>
 </div>
-<div id="outline-container-org26639c5" class="outline-3">
-<h3 id="org26639c5"><span class="section-number-3">14.1.</span> Deficiencies</h3>
+<div id="outline-container-orgf084076" class="outline-3">
+<h3 id="orgf084076"><span class="section-number-3">14.1.</span> Deficiencies</h3>
 <div class="outline-text-3" id="text-14-1">
 <p>
 The current network monitoring is rudimentary.  It could use some
@@ -8906,49 +8813,21 @@ units.
 </p>
 
 <p>
-The institute's private domain names (e.g. <code>www.small.private</code>) are
-not resolvable on Front.  Reverse domains (<code>86.177.10.in-addr.arpa</code>)
-mapping institute network addresses back to names in the private
-domain <code>small.private</code> work only on the campus Ethernet.  These nits
-might be picked when OpenVPN supports the DHCP option
-<code>rdnss-selection</code> (RFC6731), or with hard-coded <code>resolvectl</code> commands.
-</p>
-
-<p>
-The <code>./inst old dick</code> command does not break VPN connections to Dick's
-clients.  New connections cannot be created, but old connections can
-continue to work for some time.
-</p>
-
-<p>
-The <code>./inst client android dick-phone dick</code> command generates <q>.ovpn</q>
-files that require the member to remember to check the "Use this
-connection only for resources on its network" box in the IPv4 (and
-IPv6) tab(s) of the Add VPN dialog.  The command should include an
-OpenVPN setting that the NetworkManager file importer recognizes as
-the desired setting.
-</p>
-
-<p>
-The VPN service is overly complex.  The OpenVPN 2.4.7 clients allow
-multiple server addresses, but the <code>openvpn(8)</code> manual page suggests
-per connection parameters are restricted to a set that does <i>not</i>
-include the essential <code>verify-x509-name</code>.  Use the same name on
-separate certificates for Gate and Front?  Use the same certificate
-and key on Gate and Front?
+The institute's reverse domains (e.g. <code>86.177.10.in-addr.arpa</code>) are
+not available on Front, yet.
 </p>
 </div>
 </div>
-<div id="outline-container-orgfe73858" class="outline-3">
-<h3 id="orgfe73858"><span class="section-number-3">14.2.</span> More Tests</h3>
+<div id="outline-container-org86db359" class="outline-3">
+<h3 id="org86db359"><span class="section-number-3">14.2.</span> More Tests</h3>
 <div class="outline-text-3" id="text-14-2">
 <p>
 The testing process described in the previous chapter is far from
 complete.  Additional tests are needed.
 </p>
 </div>
-<div id="outline-container-orgc61d0e9" class="outline-4">
-<h4 id="orgc61d0e9"><span class="section-number-4">14.2.1.</span> Backup</h4>
+<div id="outline-container-orgf829be6" class="outline-4">
+<h4 id="orgf829be6"><span class="section-number-4">14.2.1.</span> Backup</h4>
 <div class="outline-text-4" id="text-14-2-1">
 <p>
 The <code>backup</code> command has not been tested.  It needs an encrypted
@@ -8957,8 +8836,8 @@ partition with which to sync?  And then some way to compare that to
 </p>
 </div>
 </div>
-<div id="outline-container-org6d144a5" class="outline-4">
-<h4 id="org6d144a5"><span class="section-number-4">14.2.2.</span> Restore</h4>
+<div id="outline-container-org90cf226" class="outline-4">
+<h4 id="org90cf226"><span class="section-number-4">14.2.2.</span> Restore</h4>
 <div class="outline-text-4" id="text-14-2-2">
 <p>
 The restore process has not been tested.  It might just copy <a href="Backup/"><q>Backup/</q></a>
@@ -8968,8 +8847,8 @@ perhaps permissions too.  It could also use an example
 </p>
 </div>
 </div>
-<div id="outline-container-org876c9cb" class="outline-4">
-<h4 id="org876c9cb"><span class="section-number-4">14.2.3.</span> Campus Disconnect</h4>
+<div id="outline-container-org9298478" class="outline-4">
+<h4 id="org9298478"><span class="section-number-4">14.2.3.</span> Campus Disconnect</h4>
 <div class="outline-text-4" id="text-14-2-3">
 <p>
 Email access (IMAPS) on <code>front</code> is&#x2026; difficult to test unless
@@ -8993,8 +8872,8 @@ could be used.</li>
 </div>
 </div>
 </div>
-<div id="outline-container-orgb988f90" class="outline-2">
-<h2 id="orgb988f90"><span class="section-number-2">15.</span> Appendix: The Bootstrap</h2>
+<div id="outline-container-orgd76b9d9" class="outline-2">
+<h2 id="orgd76b9d9"><span class="section-number-2">15.</span> Appendix: The Bootstrap</h2>
 <div class="outline-text-2" id="text-15">
 <p>
 Creating the private network from whole cloth (machines with recent
@@ -9014,11 +8893,11 @@ etc.: quite a bit of temporary, manual localnet configuration just to
 get to the additional packages.
 </p>
 </div>
-<div id="outline-container-org7fe37c6" class="outline-3">
-<h3 id="org7fe37c6"><span class="section-number-3">15.1.</span> The Current Strategy</h3>
+<div id="outline-container-org65958ab" class="outline-3">
+<h3 id="org65958ab"><span class="section-number-3">15.1.</span> The Current Strategy</h3>
 <div class="outline-text-3" id="text-15-1">
 <p>
-The strategy pursued in <a href="#orgbf29081">The Hardware</a> is two phase: prepare the servers
+The strategy pursued in <a href="#org5af003d">The Hardware</a> is two phase: prepare the servers
 on the Internet where additional packages are accessible, then connect
 them to the campus facilities (the private Ethernet switch, Wi-Fi AP,
 ISP), manually configure IP addresses (while the DHCP client silently
@@ -9026,8 +8905,8 @@ fails), and avoid names until BIND9 is configured.
 </p>
 </div>
 </div>
-<div id="outline-container-org94b8b56" class="outline-3">
-<h3 id="org94b8b56"><span class="section-number-3">15.2.</span> Starting With Gate</h3>
+<div id="outline-container-org55fb799" class="outline-3">
+<h3 id="org55fb799"><span class="section-number-3">15.2.</span> Starting With Gate</h3>
 <div class="outline-text-3" id="text-15-2">
 <p>
 The strategy of Starting With Gate concentrates on configuring Gate's
@@ -9071,8 +8950,8 @@ ansible-playbook -l core site.yml
 </ul>
 </div>
 </div>
-<div id="outline-container-org1c34ede" class="outline-3">
-<h3 id="org1c34ede"><span class="section-number-3">15.3.</span> Pre-provision With Ansible</h3>
+<div id="outline-container-orgcc3f919" class="outline-3">
+<h3 id="orgcc3f919"><span class="section-number-3">15.3.</span> Pre-provision With Ansible</h3>
 <div class="outline-text-3" id="text-15-3">
 <p>
 A refinement of the current strategy might avoid the need to maintain
@@ -9125,7 +9004,7 @@ routes on Front and Gate, making the simulation less&#x2026; similar.
 </div></div>
 <div id="postamble" class="status">
 <p class="author">Author: Matt Birkholz</p>
-<p class="date">Created: 2025-05-31 Sat 22:27</p>
+<p class="date">Created: 2025-06-15 Sun 12:23</p>
 <p class="validation"><a href="https://validator.w3.org/check?uri=referer">Validate</a></p>
 </div>
 </body>