Turn many relative filenames into links.
authorMatt Birkholz <matt@birchwood-abbey.net>
Thu, 28 Dec 2023 06:20:54 +0000 (23:20 -0700)
committerMatt Birkholz <matt@birchwood-abbey.net>
Thu, 28 Dec 2023 22:34:12 +0000 (15:34 -0700)
README.org

index 5476d1f17ce417c2e7595984f5d98e7775487f27..c12e708491760b36096547b907e1faec38fded92 100644 (file)
@@ -314,8 +314,8 @@ PHP) stack.
 Core provides a campus HTTP service with several virtual hosts.
 These web sites can only be accessed via the campus Ethernet or an
 institute VPN.  In either situation Core's many private domain names
-become available, e.g. =www.small.private=.  In many cases these
-domain names can be shortened e.g. to =www=.  Thus the campus home
+become available, e.g. ~www.small.private~.  In many cases these
+domain names can be shortened e.g. to ~www~.  Thus the campus home
 page is accessible in a dozen keystrokes: ~http://www/~ (plus Enter).
 
 Core's web sites:
@@ -489,9 +489,9 @@ initial, privileged user account the same name is given (e.g. as
 described in [[*The Core Machine][The Core Machine]]).  Installation may /not/ prompt and
 still create an initial user account with a distribution specific name
 (e.g. ~pi~).  Any name can be used as long as it is provided as the
-value of ~ansible_user~ in =hosts=.  Its password is specified by a
-vault-encrypted variable in the =Secret/become.yml= file.  (The
-=hosts= and =Secret/become.yml= files are described in [[*The Ansible Configuration][The Ansible
+value of ~ansible_user~ in [[file:hosts][=hosts=]].  Its password is specified by a
+vault-encrypted variable in the [[file:Secret/become.yml][=Secret/become.yml=]] file.  (The
+[[file:hosts][=hosts=]] and [[file:Secret/become.yml][=Secret/become.yml=]] files are described in [[*The Ansible Configuration][The Ansible
 Configuration]].)
 
 *** The Monkey Accounts
@@ -505,43 +505,43 @@ account is created on Front as well.
 
 The institute keeps its "master secrets" in an encrypted
 volume on an off-line hard drive, e.g. a LUKS (Linux Unified Key
-Setup) format partition on a USB pen/stick.  The =Secret/=
+Setup) format partition on a USB pen/stick.  The [[file:Secret/][=Secret/=]]
 sub-directory is actually a symbolic link to this partition's
 automatic mount point, e.g.  =/media/sysadm/ADE7-F866/=.  Unless this
-volume is mounted (unlocked) at =Secret/=, none of the ~./inst~
+volume is mounted (unlocked) at [[file:Secret/][=Secret/=]], none of the ~./inst~
 commands will work.
 
 Chief among the institute's master secrets is the SSH key authorized
 to access privileged accounts on /all/ of the institute servers.  It
-is stored in =Secret/ssh_admin/id_rsa=.  The complete list of the
+is stored in [[file:Secret/ssh_admin/id_rsa][=Secret/ssh_admin/id_rsa=]].  The complete list of the
 institute's SSH keys:
 
-  - =Secret/ssh_admin/= :: The SSH key pair for A Small Institute
+  - [[file:Secret/ssh_admin/][=Secret/ssh_admin/=]] :: The SSH key pair for A Small Institute
     Administrator.
-  - =Secret/ssh_monkey/= :: The key pair used by Monkey to update the
+  - [[file:Secret/ssh_monkey/][=Secret/ssh_monkey/=]] :: The key pair used by Monkey to update the
     website on Front (and other unprivileged tasks).
-  - =Secret/ssh_front/= :: The host key pair used by Front to
+  - [[file:Secret/ssh_front/][=Secret/ssh_front/=]] :: The host key pair used by Front to
     authenticate itself.  The automatically generated key pair is
     /not/ used.  (Thus Core's configuration does not depend on
     Front's.)
 
 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 =Secret/CA/=.
+Authority stored in [[file:Secret/CA/][=Secret/CA/=]].
 
-  - =Secret/CA/pki/ca.crt= :: The institute CA certificate, used to
+  - [[file:Secret/CA/pki/ca.crt][=Secret/CA/pki/ca.crt=]] :: The institute CA certificate, used to
     sign the other certificates.
 
-  - =Secret/CA/pki/issued/small.example.org.crt= :: The public Apache,
+  - [[file:Secret/CA/pki/issued/small.example.org.crt][=Secret/CA/pki/issued/small.example.org.crt=]] :: The public Apache,
     Postfix, and OpenVPN servers on Front.
 
-  - =Secret/CA/pki/issued/gate.small.private.crt= :: The campus
+  - [[file:Secret/CA/pki/issued/gate.small.private.crt][=Secret/CA/pki/issued/gate.small.private.crt=]] :: The campus
     OpenVPN server on Gate.
 
-  - =Secret/CA/pki/issued/core.small.private.crt= :: The campus
+  - [[file:Secret/CA/pki/issued/core.small.private.crt][=Secret/CA/pki/issued/core.small.private.crt=]] :: The campus
     Apache (thus Nextcloud), and Dovecot-IMAPd servers.
 
-  - =Secret/CA/pki/issued/core.crt= :: Core's client certificate, by
+  - [[file:Secret/CA/pki/issued/core.crt][=Secret/CA/pki/issued/core.crt=]] :: Core's client certificate, by
     which it authenticates to Front.
 
 The ~./inst client~ command creates client certificates and keys, and
@@ -555,15 +555,15 @@ membership roll (in =private/members.yml= as the value of ~revoked~).
 Finally, the institute uses an OpenPGP key to secure sensitive emails
 (containing passwords or private keys) to Core.
 
-  - =Secret/root.gnupg/= :: The "home directory" used to create the
+  - [[file:Secret/root.gnupg/][=Secret/root.gnupg/=]] :: The "home directory" used to create the
     public/secret key pair.
-  - =Secret/root-pub.pem= :: The ASCII armored OpenPGP public key for
+  - [[file:Secret/root-pub.pem][=Secret/root-pub.pem=]] :: The ASCII armored OpenPGP public key for
     e.g. ~root@core.small.private~.
-  - =Secret/root-sec.pem= :: The ASCII armored OpenPGP secret key.
+  - [[file:Secret/root-sec.pem][=Secret/root-sec.pem=]] :: The ASCII armored OpenPGP secret key.
 
-When [[*The CA Command][The CA Command]] sees an empty =Secret/CA/= directory, as
+When [[*The CA Command][The CA Command]] sees an empty [[file:Secret/CA/][=Secret/CA/=]] directory, as
 though just created by running the EasyRSA ~make-cadir~ command in
-=Secret/= (a new, encrypted volume), the ~./inst CA~ command creates
+[[file:Secret/][=Secret/=]] (a new, encrypted volume), the ~./inst CA~ command creates
 all of the certificates and keys mentioned above.  It may prompt for
 the institute's full name.
 
@@ -607,7 +607,7 @@ backup~ (/without/ ~-n~) produces the complete copy (with all the
 files mentioned in the Nextcloud database dump).
 
 #+NAME: backup
-#+CAPTION: =private/backup=
+#+CAPTION: [[file:private/backup][=private/backup=]]
 #+BEGIN_SRC sh :tangle private/backup :mkdirp yes :tangle-mode u=rw
 #!/bin/bash -e
 #
@@ -716,7 +716,7 @@ This chapter introduces Ansible variables intended to simplify
 changes, like customization for another institute's particulars.  The
 variables are separated into /public/ information (e.g. an institute's
 name) or /private/ information (e.g. a network interface address), and
-stored in separate files: =public/vars.yml= and =private/vars.yml=.
+stored in separate files: [[file:public/vars.yml][=public/vars.yml=]] and [[file:private/var.yml][=private/vars.yml=]].
 
 The example settings in this document configure VirtualBox VMs as
 described in the [[*Testing][Testing]] chapter.  For more information about how a
@@ -730,7 +730,7 @@ Ansible code.  The example used here is ~small.example.org~.  The
 following line sets ~domain_name~ to that value.  (Ansible will then
 replace ~{{ domain_name }}~ in the code with ~small.example.org~.)
 
-#+CAPTION: =public/vars.yml=
+#+CAPTION: [[file:public/vars.yml][=public/vars.yml=]]
 #+BEGIN_SRC conf :tangle public/vars.yml :mkdirp yes
 ---
 domain_name: small.example.org
@@ -792,11 +792,11 @@ example result follows the code.
 The four private networks are named and given example CIDRs in the
 code block below.  The small institute treats these addresses as
 sensitive information so the code block below "tangles" into
-=private/vars.yml= rather than =public/vars.yml=.  Two of the
+[[file:private/vars.yml][=private/vars.yml=]] rather than [[file:public/vars.yml][=public/vars.yml=]].  Two of the
 addresses are in ~192.168~ subnets because they are part of a test
 configuration using mostly-default VirtualBoxes (described [[*Testing][here]]).
 
-#+CAPTION: =private/vars.yml=
+#+CAPTION: [[file:private/vars.yml][=private/vars.yml=]]
 #+BEGIN_SRC conf :tangle private/vars.yml :tangle-mode u=rw
 ---
 private_net_cidr:           192.168.56.0/24
@@ -811,7 +811,7 @@ following boilerplate uses Ansible's ~ipaddr~ filter to set several
 corresponding variables, each with an appropriate suffix,
 e.g. ~_net_and_mask~ rather than ~_net_cidr~.
 
-#+CAPTION: =private/vars.yml=
+#+CAPTION: [[file:private/vars.yml][=private/vars.yml=]]
 #+BEGIN_SRC conf :tangle private/vars.yml
 private_net:             "{{ private_net_cidr | ipaddr('network') }}"
 private_net_mask:        "{{ private_net_cidr | ipaddr('netmask') }}"
@@ -836,7 +836,7 @@ rather than domain names, and one of the most important for secure and
 reliable operation is Front's public IP address known to the world by
 the institute's Internet domain name.
 
-#+CAPTION: =public/vars.yml=
+#+CAPTION: [[file:public/vars.yml][=public/vars.yml=]]
 #+BEGIN_SRC conf :tangle public/vars.yml
 front_addr: 192.168.15.5
 #+END_SRC
@@ -856,7 +856,7 @@ address on the public VPN, perversely called ~front_private_addr~.
 The following code block picks the obvious IP addresses for Core
 (host 1) and Gate (host 2).
 
-#+CAPTION: =private/vars.yml=
+#+CAPTION: [[file:private/vars.yml][=private/vars.yml=]]
 #+BEGIN_SRC conf :tangle private/vars.yml
 core_addr_cidr:             "{{ private_net_cidr | ipaddr('1') }}"
 gate_addr_cidr:             "{{ private_net_cidr | ipaddr('2') }}"
@@ -926,8 +926,8 @@ escalated privileges via the ~sudo~ command.
 : notebook$
 
 The password was generated by ~gpw~, saved in the administrator's
-password keep, and later added to =Secret/become.yml= as shown below.
-(Producing a working Ansible configuration with =Secret/become.yml=
+password keep, and later added to [[file:Secret/become.yml][=Secret/become.yml=]] as shown below.
+(Producing a working Ansible configuration with [[file:Secret/become.yml][=Secret/become.yml=]]
 file is described in [[*The Ansible Configuration][The Ansible Configuration]].)
 
 : notebook$ gpw 1 16
@@ -938,7 +938,7 @@ file is described in [[*The Ansible Configuration][The Ansible Configuration]].)
 
 After creating the ~sysadm~ account on the droplet, the administrator
 concatenated a personal public ssh key and the key found in
-=Secret/ssh_admin/= (created by [[*The CA Command][The CA Command]]) into an =admin_keys=
+[[file:Secret/ssh_admin/][=Secret/ssh_admin/=]] (created by [[*The CA Command][The CA Command]]) into an =admin_keys=
 file, copied it to the droplet, and installed it as the
 =authorized_keys= for ~sysadm~.
 
@@ -1011,8 +1011,8 @@ and a privileged account named ~sysadm~ was created (per the policy in
 : Is the information correct? [Y/n] 
 
 The password was generated by ~gpw~, saved in the administrator's
-password keep, and later added to =Secret/become.yml= as shown below.
-(Producing a working Ansible configuration with =Secret/become.yml=
+password keep, and later added to [[file:Secret/become.yml][=Secret/become.yml=]] as shown below.
+(Producing a working Ansible configuration with [[file:Secret/become.yml][=Secret/become.yml=]]
 file is described in [[*The Ansible Configuration][The Ansible Configuration]].)
 
 : notebook$ gpw 1 16
@@ -1037,7 +1037,7 @@ final configuration "in position" (on a frontier).
 : _                  libapache2-mod-php
 
 Next, the administrator concatenated a personal public ssh key and the
-key found in =Secret/ssh_admin/= (created by [[*The CA Command][The CA Command]]) into an
+key found in [[file:Secret/ssh_admin/][=Secret/ssh_admin/=]] (created by [[*The CA Command][The CA Command]]) into an
 =admin_keys= file, copied it to Core, and installed it as the
 =authorized_keys= for ~sysadm~.
 
@@ -1156,8 +1156,8 @@ and a privileged account named ~sysadm~ was created (per the policy in
 : Is the information correct? [Y/n] 
 
 The password was generated by ~gpw~, saved in the administrator's
-password keep, and later added to =Secret/become.yml= as shown below.
-(Producing a working Ansible configuration with =Secret/become.yml=
+password keep, and later added to [[file:Secret/become.yml][=Secret/become.yml=]] as shown below.
+(Producing a working Ansible configuration with [[file:Secret/become.yml][=Secret/become.yml=]]
 file is described in [[*The Ansible Configuration][The Ansible Configuration]].)
 
 : notebook$ gpw 1 16
@@ -1173,7 +1173,7 @@ cable modem and installed them as shown below.
 : $ sudo apt install openssh-server isc-dhcp-server netplan.io
 
 Next, the administrator concatenated a personal public ssh key and the
-key found in =Secret/ssh_admin/= (created by [[*The CA Command][The CA Command]]) into an
+key found in [[file:Secret/ssh_admin/][=Secret/ssh_admin/=]] (created by [[*The CA Command][The CA Command]]) into an
 =admin_keys= file, copied it to Gate, and installed it as the
 =authorized_keys= for ~sysadm~.
 
@@ -1249,9 +1249,9 @@ first ~front~ role tasks are to include these files (described in [[*The Particu
 Particulars]] and [[*Account Management][Account Management]]).
 
 The code block below is the first to tangle into
-=roles/front/tasks/main.yml=.
+[[file:roles/front/tasks/main.yml][=roles/front/tasks/main.yml=]].
 
-#+CAPTION: =roles/front/tasks/main.yml=
+#+CAPTION: [[file:roles/front/tasks/main.yml][=roles/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml :mkdirp yes
 ---
 - name: Include public variables.
@@ -1273,7 +1273,7 @@ This task ensures that Front's =/etc/hostname= and =/etc/mailname= are
 correct.  The correct =/etc/mailname= is essential to proper email
 delivery.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 - name: Configure hostname.
   become: yes
@@ -1286,7 +1286,7 @@ delivery.
   notify: Update hostname.
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml :mkdirp yes
 ---
 - name: Update hostname.
@@ -1310,7 +1310,7 @@ to enable "persistent logging", yet).  In Debian 12 there is a
 These tasks are included in all of the roles, and so are given in a
 separate code block named ~enable-resolved~.[fn:1]
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml :noweb yes
 <<enable-resolved>>
 #+END_SRC
@@ -1358,7 +1358,7 @@ The administrator often needs to read (directories of) log files owned
 by groups ~root~ and ~adm~.  Adding the administrator's account to
 these groups speeds up debugging.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Add {{ ansible_user }} to system groups.
@@ -1373,9 +1373,9 @@ these groups speeds up debugging.
 
 The SSH service on Front needs to be known to Monkey.  The following
 tasks ensure this by replacing the automatically generated keys with
-those stored in =Secret/ssh_front/etc/ssh/= and restarting the server.
+those stored in [[file:Secret/ssh_front/etc/ssh/][=Secret/ssh_front/etc/ssh/=]] and restarting the server.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Install SSH host keys.
@@ -1394,7 +1394,7 @@ those stored in =Secret/ssh_front/etc/ssh/= and restarting the server.
   notify: Reload SSH server.
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml
 
 - name: Reload SSH server.
@@ -1415,7 +1415,7 @@ described in [[apache2-front][*Configure Apache2]]).  To do that without needing
 password, the ~monkey~ account on Front should authorize Monkey's SSH
 key on Core.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Create monkey.
@@ -1445,7 +1445,7 @@ key on Core.
 
 Monkey uses Rsync to keep the institute's public web site up-to-date.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Install rsync.
@@ -1457,7 +1457,7 @@ Monkey uses Rsync to keep the institute's public web site up-to-date.
 
 The institute prefers to install security updates as soon as possible.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Install basic software.
@@ -1472,7 +1472,7 @@ start delivering email immediately, /without/ returning "no such
 recipient" replies.  The [[*Account Management][Account Management]] chapter describes the
 ~members~ and ~usernames~ variables used below.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Create user accounts.
@@ -1512,7 +1512,7 @@ trustworthy, so its certificate is added to Front's set of trusted
 CAs.  More information about how the small institute manages its
 X.509 certificates is available in [[*Keys][Keys]].
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Trust the institute CA.
@@ -1526,7 +1526,7 @@ X.509 certificates is available in [[*Keys][Keys]].
   notify: Update CAs.
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml
 
 - name: Update CAs.
@@ -1541,7 +1541,7 @@ authenticate themselves to institute clients.  They share the
 =/etc/server.crt= and =/etc/server.key= files, the latter only
 readable by ~root~.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Install server certificate/key.
@@ -1653,7 +1653,7 @@ The following Ansible tasks install Postfix, modify
 =/etc/postfix/main.cf= according to the settings given above, and
 start and enable the service.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml :noweb yes
 
 - name: Install Postfix.
@@ -1686,7 +1686,7 @@ start and enable the service.
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml
 
 - name: Restart Postfix.
@@ -1717,7 +1717,7 @@ do /not/ include the crucial ~root~ alias that forwards to the
 administrator.  It could be included here or in a separate block
 created by a more specialized role.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 - name: Install institute email aliases.
   become: yes
@@ -1733,7 +1733,7 @@ created by a more specialized role.
   notify: New aliases.
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml
 
 - name: New aliases.
@@ -1762,7 +1762,7 @@ The following Ansible tasks install Dovecot's IMAP daemon and its
 =/etc/dovecot/local.conf= configuration file, then starts the service
 and enables it to start at every reboot.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml :noweb yes
 
 - name: Install Dovecot IMAPd.
@@ -1789,7 +1789,7 @@ and enables it to start at every reboot.
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml
 
 - name: Restart Dovecot.
@@ -1955,7 +1955,7 @@ Ansible installs the configuration above in
 e.g. =/etc/apache2/sites-available/small.example.org.conf= and runs
 ~a2ensite -q small.example.org~ to enable it.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml :noweb yes
 
 - name: Install Apache2.
@@ -2000,7 +2000,7 @@ e.g. =/etc/apache2/sites-available/small.example.org.conf= and runs
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml
 
 - name: Restart Apache2.
@@ -2013,7 +2013,7 @@ e.g. =/etc/apache2/sites-available/small.example.org.conf= and runs
 Furthermore, the default web site and its HTTPS version is disabled so
 that it does not interfere with its replacement.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Disable default vhosts.
@@ -2029,7 +2029,7 @@ The redundant default =other-vhosts-access-log= configuration option
 is also disabled.  There are no other virtual hosts, and it stores the
 same records as =access.log=.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Disable other-vhosts-access-log option.
@@ -2043,7 +2043,7 @@ same records as =access.log=.
 Finally, the ~UserDir~ is created and populated with symbolic links to
 the users' =~/Public/HTML/= directories.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Create UserDir.
@@ -2135,7 +2135,7 @@ tls-auth ta.key 0
 Finally, here are the tasks (and handler) required to install and
 configure the OpenVPN server on Front.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml :noweb yes
 
 - name: Install OpenVPN.
@@ -2213,7 +2213,7 @@ configure the OpenVPN server on Front.
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml
 
 - name: Restart OpenVPN.
@@ -2253,7 +2253,7 @@ starts).
 
 The first step is to install Kamailio.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml :noweb yes
 
 - name: Install Kamailio.
@@ -2266,7 +2266,7 @@ Kamailio will be listening, the ~tun~ device created by OpenVPN.  The
 added configuration settings inform Systemd that Kamailio should not
 be started before the ~tun~ device has appeared.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml
 
 - name: Create Kamailio/Systemd configuration drop.
@@ -2286,7 +2286,7 @@ be started before the ~tun~ device has appeared.
   notify: Reload Systemd.
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml
 
 - name: Reload Systemd.
@@ -2296,7 +2296,7 @@ be started before the ~tun~ device has appeared.
 
 Finally, Kamailio can be configured and started.
 
-#+CAPTION: =roles_t/front/tasks/main.yml=
+#+CAPTION: [[file:roles_t/front/tasks/main.yml][=roles_t/front/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/tasks/main.yml :noweb yes
 
 - name: Configure Kamailio.
@@ -2315,7 +2315,7 @@ Finally, Kamailio can be configured and started.
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/front/handlers/main.yml=
+#+CAPTION: [[file:roles_t/front/handlers/main.yml][=roles_t/front/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/front/handlers/main.yml
 
 - name: Restart Kamailio.
@@ -2339,7 +2339,7 @@ account.  (For details, see [[*The Core Machine][The Core Machine]].)
 The first task, as in [[*The Front Role][The Front Role]], is to include the institute
 particulars and membership roll.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml :mkdirp yes
 ---
 - name: Include public variables.
@@ -2361,7 +2361,7 @@ private domain names, e.g. to ~dick@small.example.org~ as well as
 ~dick@small.private~.  The correct =/etc/mailname= is essential to
 proper email delivery.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Configure hostname.
@@ -2375,7 +2375,7 @@ proper email delivery.
   notify: Update hostname.
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml :mkdirp yes
 ---
 - name: Update hostname.
@@ -2388,7 +2388,7 @@ proper email delivery.
 Core starts the ~systemd-networkd~ and ~systemd-resolved~ service
 units on boot.  See [[resolved-front][Enable Systemd Resolved]].
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml :noweb yes
 <<enable-resolved>>
 #+END_SRC
@@ -2399,7 +2399,7 @@ Core runs the campus name server, so Resolved is configured to use it
 (or ~dns.google~), to include the institute's domain in its search
 list, and to disable its cache and stub listener.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Configure resolved.
@@ -2419,7 +2419,7 @@ list, and to disable its cache and stub listener.
   - Restart Systemd resolved.
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Reload Systemd.
@@ -2443,7 +2443,7 @@ 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.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install netplan.
@@ -2469,7 +2469,7 @@ VPN.
   notify: Apply netplan.
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Apply netplan.
@@ -2485,14 +2485,14 @@ network addresses to hosts plugged into the private Ethernet as well
 as advertising local net services, especially the local Domain Name
 Service.
 
-The example configuration file, =private/core-dhcpd.conf=, uses
+The example configuration file, [[file:private/core-dhcpd.conf][=private/core-dhcpd.conf=]], uses
 RFC3442's extension to encode a second (non-default) static route.
 The default route is through the campus ISP at Gate.  A second route
 directs campus traffic to the Front VPN through Core.  This is just an
 example file.  The administrator adds and removes actual machines from
-the actual =private/core-dhcpd.conf= file.
+the actual [[file:private/core-dhcpd.conf][=private/core-dhcpd.conf=]] file.
 
-#+CAPTION: =private/core-dhcpd.conf=
+#+CAPTION: [[file:private/core-dhcpd.conf][=private/core-dhcpd.conf=]]
 #+BEGIN_SRC conf :tangle private/core-dhcpd.conf :tangle-mode u=rw
 option domain-name "small.private";
 option domain-name-servers 192.168.56.1;
@@ -2525,9 +2525,9 @@ host server {
 #+END_SRC
 
 The following tasks install the ISC's DHCP server and configure it
-with the real =private/core-dhcpd.conf= (/not/ the example above).
+with the real [[file:private/core-dhcpd.conf][=private/core-dhcpd.conf=]] (/not/ the example above).
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install DHCP server.
@@ -2557,7 +2557,7 @@ with the real =private/core-dhcpd.conf= (/not/ the example above).
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Restart DHCP server.
@@ -2576,7 +2576,7 @@ addresses to private domain names.
 
 The following tasks install and configure BIND9 on Core.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml :noweb yes
 
 - name: Install BIND9.
@@ -2615,7 +2615,7 @@ The following tasks install and configure BIND9 on Core.
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
   - name: Reload BIND9.
@@ -2699,7 +2699,7 @@ zone "{{ campus_vpn_net_cidr | ipaddr('revdns')
 };
 #+END_SRC
 
-#+CAPTION: =private/db.domain=
+#+CAPTION: [[file:private/db.domain][=private/db.domain=]]
 #+BEGIN_SRC conf :tangle private/db.domain :tangle-mode u=rw
 ;
 ; BIND data file for a small institute's PRIVATE domain names.
@@ -2727,7 +2727,7 @@ core      IN      A       192.168.56.1
 gate   IN      A       192.168.56.2
 #+END_SRC
 
-#+CAPTION: =private/db.private=
+#+CAPTION: [[file:private/db.private][=private/db.private=]]
 #+BEGIN_SRC conf :tangle private/db.private :tangle-mode u=rw
 ;
 ; BIND reverse data file for a small institute's private Ethernet.
@@ -2746,7 +2746,7 @@ $TTL      7200
 2      IN      PTR     gate.small.private.
 #+END_SRC
 
-#+CAPTION: =private/db.public_vpn=
+#+CAPTION: [[file:private/db.public_vpn][=private/db.public_vpn=]]
 #+BEGIN_SRC conf :tangle private/db.public_vpn :tangle-mode u=rw
 ;
 ; BIND reverse data file for a small institute's public VPN.
@@ -2765,7 +2765,7 @@ $TTL      7200
 2      IN      PTR     core-p.small.private.
 #+END_SRC
 
-#+CAPTION: =private/db.campus_vpn=
+#+CAPTION: [[file:private/db.campus_vpn][=private/db.campus_vpn=]]
 #+BEGIN_SRC conf :tangle private/db.campus_vpn :tangle-mode u=rw
 ;
 ; BIND reverse data file for a small institute's campus VPN.
@@ -2789,7 +2789,7 @@ The administrator often needs to read (directories of) log files owned
 by groups ~root~ and ~adm~.  Adding the administrator's account to
 these groups speeds up debugging.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Add {{ ansible_user }} to system groups.
@@ -2808,7 +2808,7 @@ system account named ~monkey~.  One of Monkey's more important jobs on
 Core is to run ~rsync~ to update the public web site on Front (as
 described in [[apache2-core][*Configure Apache2]]).
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Create monkey.
@@ -2868,7 +2868,7 @@ described in [[apache2-core][*Configure Apache2]]).
 
 The institute prefers to install security updates as soon as possible.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install basic software.
@@ -2881,7 +2881,7 @@ The institute prefers to install security updates as soon as possible.
 The ~expect~ program is used by [[* The Institute Commands][The Institute Commands]] to interact
 with Nextcloud on the command line.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install expect.
@@ -2895,7 +2895,7 @@ User accounts are created immediately so that backups can begin
 restoring as soon as possible.  The [[*Account Management][Account Management]] chapter
 describes the ~members~ and ~usernames~ variables.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Create user accounts.
@@ -2935,7 +2935,7 @@ trustworthy, so its certificate is added to Core's set of trusted
 CAs.  More information about how the small institute manages its
 X.509 certificates is available in [[*Keys][Keys]].
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Trust the institute CA.
@@ -2949,7 +2949,7 @@ X.509 certificates is available in [[*Keys][Keys]].
   notify: Update CAs.
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Update CAs.
@@ -2963,7 +2963,7 @@ The servers on Core use the same certificate (and key) to authenticate
 themselves to institute clients.  They share the =/etc/server.crt= and
 =/etc/server.key= files, the latter only readable by ~root~.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install server certificate/key.
@@ -2988,7 +2988,7 @@ themselves to institute clients.  They share the =/etc/server.crt= and
 Core uses NTP to provide a time synchronization service to the campus.
 The default daemon's default configuration is fine.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml :noweb yes
 
 - name: Install NTP.
@@ -3071,7 +3071,7 @@ The following Ansible tasks install Postfix, modify
 enable the service.  Whenever =/etc/postfix/transport= is changed, the
 ~postmap transport~ command must also be run.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml :noweb yes
 
 - name: Install Postfix.
@@ -3105,7 +3105,7 @@ enable the service.  Whenever =/etc/postfix/transport= is changed, the
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Restart Postfix.
@@ -3131,7 +3131,7 @@ to e.g. ~monkey~.  The following aliases are installed in
 =/etc/aliases= with a special marker so that additional blocks can be
 installed by more specialized roles.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install institute email aliases.
@@ -3148,7 +3148,7 @@ installed by more specialized roles.
   notify: New aliases.
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: New aliases.
@@ -3177,7 +3177,7 @@ The following Ansible tasks install Dovecot's IMAP daemon and its
 =/etc/dovecot/local.conf= configuration file, then starts the service
 and enables it to start at every reboot.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml :noweb yes
 
 - name: Install Dovecot IMAPd.
@@ -3203,7 +3203,7 @@ and enables it to start at every reboot.
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Restart Dovecot.
@@ -3270,7 +3270,7 @@ institute who may wish to run their own fetchmail job on their
 notebook, only members with a ~fetchmail_password~ key will be
 provided the Core service.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml :noweb yes
 
 - name: Install fetchmail.
@@ -3320,7 +3320,7 @@ provided the Core service.
 Finally, any former member's Fetchmail service on Core should be
 stopped and disabled from restarting at boot, deleted even.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Stop former user fetchmail services.
@@ -3479,7 +3479,7 @@ The tasks below install Apache2 and edit its default configuration.
 The global ~ServerName~ directive must be deleted because it seems to
 interfere with mapping URLs to the correct virtual host.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install Apache2.
@@ -3507,7 +3507,7 @@ With Apache installed there is a =/etc/apache/sites-available/=
 directory into which the above site configurations can be installed.
 The ~a2ensite~ command enables them.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml :noweb yes
 
 - name: Install live web site.
@@ -3553,7 +3553,7 @@ The ~a2ensite~ command enables them.
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Restart Apache2.
@@ -3570,7 +3570,7 @@ Monkey on Core runs =/usr/local/sbin/webupdate= every 15 minutes via a
 =/home/www/= on Front.
 
 #+NAME: webupdate
-#+CAPTION: =private/webupdate=
+#+CAPTION: [[file:private/webupdate][=private/webupdate=]]
 #+BEGIN_SRC sh
 #!/bin/bash -e
 #
@@ -3584,11 +3584,11 @@ rsync -avz --delete --chmod=g-w         \
        ./ {{ domain_name }}:/home/www/
 #+END_SRC
 
-The following tasks install the =webupdate= script from =private/=,
+The following tasks install the =webupdate= script from [[file:private/][=private/=]],
 and create Monkey's ~cron~ job.  An example =webupdate= script is
 provided [[webupdate][here]].
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: "Install Monkey's webupdate script."
@@ -3643,7 +3643,7 @@ tls-auth ta.key 1
 The tasks that install and configure the OpenVPN client configuration
 for Core.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml :noweb yes
 
 - name: Install OpenVPN.
@@ -3693,7 +3693,7 @@ for Core.
     enabled: yes
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Restart OpenVPN.
@@ -3718,7 +3718,7 @@ on the ~sensors~ command (from the ~lm-sensors~ package).  The custom
 version (below) is installed in =/usr/local/sbin/inst_sensors= on both
 Core and Campus (and thus Gate) machines.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install NAGIOS4.
@@ -3770,7 +3770,7 @@ Core and Campus (and thus Gate) machines.
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Reload NAGIOS4.
@@ -3787,7 +3787,7 @@ Core.  The monitors are simple, local plugins, and the block is very
 similar to the default =objects/localhost.cfg= file.  The commands
 used here /may/ specify plugin arguments.
 
-#+CAPTION: =roles_t/core/templates/nagios.cfg=
+#+CAPTION: [[file:roles_t/core/templates/nagios.cfg][=roles_t/core/templates/nagios.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/core/templates/nagios.cfg :mkdirp yes
 define host {
     use                     linux-server
@@ -3859,7 +3859,7 @@ The ~check_sensors~ plugin is included in the package
 small institute substitutes a slightly modified version,
 ~inst_sensors~, that reports core CPU temperatures.
 
-#+CAPTION: =roles_t/core/files/inst_sensors=
+#+CAPTION: [[file:roles_t/core/files/inst_sensors][=roles_t/core/files/inst_sensors=]]
 #+BEGIN_SRC sh :tangle roles_t/core/files/inst_sensors
 #!/bin/sh
 
@@ -3942,7 +3942,7 @@ esac
 The following block defines the command and monitors it (locally) on
 Core.
 
-#+CAPTION: =roles_t/core/templates/nagios.cfg=
+#+CAPTION: [[file:roles_t/core/templates/nagios.cfg][=roles_t/core/templates/nagios.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/core/templates/nagios.cfg
 
 define command {
@@ -3979,7 +3979,7 @@ on each campus host by the campus role's [[*Configure NRPE][Configure NRPE]] tas
 Define the monitored host, ~gate~.  Monitor its response to network
 pings.
 
-#+CAPTION: =roles_t/core/templates/nagios.cfg=
+#+CAPTION: [[file:roles_t/core/templates/nagios.cfg][=roles_t/core/templates/nagios.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/core/templates/nagios.cfg
 
 define host {
@@ -3992,14 +3992,14 @@ define host {
 For all campus NRPE servers: an ~inst_root~ command to check the free
 space on the root partition.
 
-#+CAPTION: =roles_t/campus/files/nrpe.cfg=
+#+CAPTION: [[file:roles_t/campus/files/nrpe.cfg][=roles_t/campus/files/nrpe.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/files/nrpe.cfg :mkdirp yes
 command[inst_root]=/usr/lib/nagios/plugins/check_disk -w 20% -c 10% -p /
 #+END_SRC
 
 Monitor ~inst_root~ on Gate.
 
-#+CAPTION: =roles_t/core/templates/nagios.cfg=
+#+CAPTION: [[file:roles_t/core/templates/nagios.cfg][=roles_t/core/templates/nagios.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/core/templates/nagios.cfg
 
 define service {
@@ -4012,7 +4012,7 @@ define service {
 
 Monitor ~check_load~ on Gate.
 
-#+CAPTION: =roles_t/core/templates/nagios.cfg=
+#+CAPTION: [[file:roles_t/core/templates/nagios.cfg][=roles_t/core/templates/nagios.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/core/templates/nagios.cfg
 
 define service {
@@ -4025,7 +4025,7 @@ define service {
 
 Monitor ~check_zombie_procs~ and ~check_total_procs~ on Gate.
 
-#+CAPTION: =roles_t/core/templates/nagios.cfg=
+#+CAPTION: [[file:roles_t/core/templates/nagios.cfg][=roles_t/core/templates/nagios.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/core/templates/nagios.cfg
 
 define service {
@@ -4046,14 +4046,14 @@ define service {
 For all campus NRPE servers: an ~inst_swap~ command to check the swap
 usage.
 
-#+CAPTION: =roles_t/campus/files/nrpe.cfg=
+#+CAPTION: [[file:roles_t/campus/files/nrpe.cfg][=roles_t/campus/files/nrpe.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/files/nrpe.cfg
 command[inst_swap]=/usr/lib/nagios/plugins/check_swap -w 20% -c 10%
 #+END_SRC
 
 Monitor ~inst_swap~ on Gate.
 
-#+CAPTION: =roles_t/core/templates/nagios.cfg=
+#+CAPTION: [[file:roles_t/core/templates/nagios.cfg][=roles_t/core/templates/nagios.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/core/templates/nagios.cfg
 
 define service {
@@ -4067,14 +4067,14 @@ define service {
 For all campus NRPE servers: an ~inst_sensors~ command to report core
 CPU temperatures.
 
-#+CAPTION: =roles_t/campus/files/nrpe.cfg=
+#+CAPTION: [[file:roles_t/campus/files/nrpe.cfg][=roles_t/campus/files/nrpe.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/files/nrpe.cfg
 command[inst_sensors]=/usr/local/sbin/inst_sensors
 #+END_SRC
 
 Monitor ~inst_sensors~ on Gate.
 
-#+CAPTION: =roles_t/core/templates/nagios.cfg=
+#+CAPTION: [[file:roles_t/core/templates/nagios.cfg][=roles_t/core/templates/nagios.cfg=]]
 #+BEGIN_SRC conf :tangle roles_t/core/templates/nagios.cfg
 
 define service {
@@ -4087,10 +4087,10 @@ define service {
 
 ** Configure Backups
 
-The following task installs the =backup= script from =private/=.  An
+The following task installs the =backup= script from [[file:private/][=private/=]].  An
 example script is provided in [[backup][here]].
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install backup script.
@@ -4116,7 +4116,7 @@ The Ansible code contained herein prepares Core to run Nextcloud by
 installing required software packages, configuring the web server, and
 installing a cron job.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install packages required by Nextcloud.
@@ -4130,7 +4130,7 @@ installing a cron job.
 
 Next, a number of Apache2 modules are enabled.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Enable Apache2 modules for Nextcloud.
@@ -4146,7 +4146,7 @@ and enabled with ~a2ensite~.  The same configuration lines are given
 in the "Installation on Linux" section of the Nextcloud Server
 Administration Guide (sub-section [[https://docs.nextcloud.com/server/latest/admin_manual/installation/source_installation.html][Apache Web server configuration]]).
 
-#+CAPTION: =roles_t/core/files/nextcloud.conf=
+#+CAPTION: [[file:roles_t/core/files/nextcloud.conf][=roles_t/core/files/nextcloud.conf=]]
 #+BEGIN_SRC conf :tangle roles_t/core/files/nextcloud.conf
 Alias /nextcloud "/var/www/nextcloud/"
 
@@ -4161,7 +4161,7 @@ Alias /nextcloud "/var/www/nextcloud/"
 </Directory>
 #+END_SRC
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install Nextcloud web configuration.
@@ -4184,7 +4184,7 @@ of the "Apache Web server configuration" subsection.  The prescribed
 rewrite rules are included in a ~Directory~ block for the default
 virtual host's document root.
 
-#+CAPTION: =roles_t/core/files/nextcloud.conf=
+#+CAPTION: [[file:roles_t/core/files/nextcloud.conf][=roles_t/core/files/nextcloud.conf=]]
 #+BEGIN_SRC conf :tangle roles_t/core/files/nextcloud.conf
 
 <Directory /var/www/html/>
@@ -4208,7 +4208,7 @@ recommended by Nextcloud 20's Settings > Administration > Overview web
 page.  The following portion of =nextcloud.conf= sets a
 ~Strict-Transport-Security~ header with a ~max-age~ of 6 months.
 
-#+CAPTION: =roles_t/core/files/nextcloud.conf=
+#+CAPTION: [[file:roles_t/core/files/nextcloud.conf][=roles_t/core/files/nextcloud.conf=]]
 #+BEGIN_SRC conf :tangle roles_t/core/files/nextcloud.conf
 
 <IfModule mod_headers.c>
@@ -4222,7 +4222,7 @@ web server's user ~www-data~ and the ~www-data~ group.  The
 administrator is added to this group to ease (speed) the debugging of
 cloud FUBARs.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Add {{ ansible_user }} to web server group.
@@ -4236,7 +4236,7 @@ cloud FUBARs.
 Nextcloud is configured with a cron job to run periodic background
 jobs.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Create Nextcloud cron job.
@@ -4252,10 +4252,10 @@ jobs.
 
 Nextcloud's MariaDB database (and user) are created by the following
 tasks.  The user's password is taken from the ~nextcloud_dbpass~
-variable, kept in =private/vars.yml=, and generated e.g. with
+variable, kept in [[file:private/vars.yml][=private/vars.yml=]], and generated e.g. with
 the ~apg -n 1 -x 12 -m 12~ command.
 
-#+CAPTION: =private/vars.yml=
+#+CAPTION: [[file:private/vars.yml][=private/vars.yml=]]
 #+BEGIN_SRC conf :tangle private/vars.yml
 nextcloud_dbpass:           ippAgmaygyob
 #+END_SRC
@@ -4302,7 +4302,7 @@ Finally, a symbolic link positions =/Nextcloud/nextcloud/= at
 Nextcloud itself should always believe that =/var/www/nextcloud/= is
 its document root.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Link /var/www/nextcloud.
@@ -4320,7 +4320,7 @@ its document root.
 The following tasks set a number of PHP parameters for better
 performance, as recommended by Nextcloud.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Set PHP memory_limit for Nextcloud.
@@ -4507,7 +4507,7 @@ Nextcloud was installed, so the first "afterwards" task probes for
 ~nextcloud~ variable.  The ~nextcloud.stat.exists~ condition on the
 afterwards tasks causes them to skip rather than fail.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Test for /Nextcloud/nextcloud/.
@@ -4534,7 +4534,7 @@ variables ~domain_priv~ and ~nextcloud_dbpass~.  The
 ~overwrite.cli.url~ setting is fixed by the tasks that implement
 Pretty URLs (below).
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Configure Nextcloud trusted domains.
@@ -4564,7 +4564,7 @@ The institute uses the ~php-apcu~ package to provide Nextcloud with a
 local memory cache.  The following ~memcache.local~ Nextcloud setting
 enables it.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Configure Nextcloud memcache.
@@ -4584,7 +4584,7 @@ and server configuration" chapter in the Nextcloud 22 Server
 Administration Guide.  Two settings are updated: ~overwrite.cli.url~
 and ~htaccess.RewriteBase~.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Configure Nextcloud for Pretty URLs.
@@ -4608,12 +4608,12 @@ and ~htaccess.RewriteBase~.
 The institute sets Nextcloud's ~default_phone_region~ mainly to avoid
 a complaint on the Settings > Administration > Overview web page.
 
-#+CAPTION: =private/vars.yml=
+#+CAPTION: [[file:private/vars.yml][=private/vars.yml=]]
 #+BEGIN_SRC conf :tangle private/vars.yml
 nextcloud_region:           US
 #+END_SRC
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Configure Nextcloud phone region.
@@ -4634,7 +4634,7 @@ readable).  This file is needed by the institute's ~backup~ command,
 so ~./inst config~ and in particular these next two tasks need to
 run before the next backup.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Create /Nextcloud/dbbackup.cnf.
@@ -4687,7 +4687,7 @@ configurations, etc.
 
 The following should be familiar boilerplate by now.
 
-#+CAPTION: =roles_t/gate/tasks/main.yml=
+#+CAPTION: [[file:roles_t/gate/tasks/main.yml][=roles_t/gate/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/gate/tasks/main.yml :mkdirp yes
 ---
 - name: Include public variables.
@@ -4709,10 +4709,10 @@ campus Ethernet and WiFi.  =/etc/netplan/60-isp.yaml= is expected to
 be revised more frequently as the campus ISP changes.
 
 Netplan is configured to identify the interfaces by their MAC
-addresses, which must be provided in =private/vars.yml=, as in the
+addresses, which must be provided in [[file:private/vars.yml][=private/vars.yml=]], as in the
 example code here.
 
-#+CAPTION: =private/vars.yml=
+#+CAPTION: [[file:private/vars.yml][=private/vars.yml=]]
 #+BEGIN_SRC conf :tangle private/vars.yml
 gate_lan_mac:               ff:ff:ff:ff:ff:ff
 gate_wifi_mac:              ff:ff:ff:ff:ff:ff
@@ -4722,7 +4722,7 @@ gate_isp_mac:               ff:ff:ff:ff:ff:ff
 The following tasks install the two configuration files and apply the
 new network plan.
 
-#+CAPTION: =roles_t/gate/tasks/main.yml=
+#+CAPTION: [[file:roles_t/gate/tasks/main.yml][=roles_t/gate/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/gate/tasks/main.yml
 
 - name: Install netplan (gate).
@@ -4776,7 +4776,7 @@ new network plan.
   notify: Apply netplan.
 #+END_SRC
 
-#+CAPTION: =roles_t/gate/handlers/main.yml=
+#+CAPTION: [[file:roles_t/gate/handlers/main.yml][=roles_t/gate/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/gate/handlers/main.yml :mkdirp yes
 ---
 - name: Apply netplan.
@@ -4871,7 +4871,7 @@ command after Gate is configured or new gate is "in position"
 
 : sudo ufw enable
 
-#+CAPTION: =roles_t/gate/tasks/main.yml=
+#+CAPTION: [[file:roles_t/gate/tasks/main.yml][=roles_t/gate/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/gate/tasks/main.yml :noweb yes
 
 - name: Install UFW.
@@ -4927,7 +4927,7 @@ specifically the sole subnet host: ~wifi_wan_name~ is any word
 appropriate for identifying the Wi-Fi AP, and ~wifi_wan_mac~ is the
 AP's MAC address.
 
-#+CAPTION: =private/vars.yml=
+#+CAPTION: [[file:private/vars.yml][=private/vars.yml=]]
 #+BEGIN_SRC conf :tangle private/vars.yml
 wifi_wan_mac:               94:83:c4:19:7d:57
 wifi_wan_name:              campus-wifi-ap
@@ -4946,7 +4946,7 @@ command would not be necessary.
 Installation and configuration of the DHCP daemon follows.  Note that
 the daemon listens /only/ on the Gate-WiFi network interface.
 
-#+CAPTION: =roles_t/gate/tasks/main.yml=
+#+CAPTION: [[file:roles_t/gate/tasks/main.yml][=roles_t/gate/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/gate/tasks/main.yml
 
 - name: Install DHCP server.
@@ -4991,7 +4991,7 @@ the daemon listens /only/ on the Gate-WiFi network interface.
     enabled: yes
 #+END_SRC
 
-#+CAPTION: =roles_t/gate/handlers/main.yml=
+#+CAPTION: [[file:roles_t/gate/handlers/main.yml][=roles_t/gate/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/gate/handlers/main.yml
 
 - name: Restart DHCP server.
@@ -5008,7 +5008,7 @@ to authenticate itself to its clients.  It uses the =/etc/server.crt=
 and =/etc/server.key= files just because the other servers (on Core
 and Front) do.
 
-#+CAPTION: =roles_t/gate/tasks/main.yml=
+#+CAPTION: [[file:roles_t/gate/tasks/main.yml][=roles_t/gate/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/gate/tasks/main.yml
 
 - name: Install server certificate/key.
@@ -5067,7 +5067,7 @@ tls-auth ta.key 0
 Finally, here are the tasks (and handler) required to install and
 configure the OpenVPN server on Gate.
 
-#+CAPTION: =roles_t/gate/tasks/main.yml=
+#+CAPTION: [[file:roles_t/gate/tasks/main.yml][=roles_t/gate/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/gate/tasks/main.yml :noweb yes
 
 - name: Install OpenVPN.
@@ -5118,7 +5118,7 @@ configure the OpenVPN server on Gate.
   notify: Restart OpenVPN.
 #+END_SRC
 
-#+CAPTION: =roles_t/gate/handlers/main.yml=
+#+CAPTION: [[file:roles_t/gate/handlers/main.yml][=roles_t/gate/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/gate/handlers/main.yml
 
 - name: Restart OpenVPN.
@@ -5146,7 +5146,7 @@ configured manually.
 
 The following should be familiar boilerplate by now.
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml :mkdirp yes
 ---
 - name: Include public variables.
@@ -5159,7 +5159,7 @@ The following should be familiar boilerplate by now.
 
 Clients should be using the expected host name.
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml
 
 - name: Configure hostname.
@@ -5177,7 +5177,7 @@ Clients should be using the expected host name.
 
 #+END_SRC
 
-#+CAPTION: =roles_t/campus/handlers/main.yml=
+#+CAPTION: [[file:roles_t/campus/handlers/main.yml][=roles_t/campus/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/handlers/main.yml :mkdirp yes
 ---
 - name: Update hostname.
@@ -5190,7 +5190,7 @@ Clients should be using the expected host name.
 Campus machines start the ~systemd-networkd~ and ~systemd-resolved~
 service units on boot.  See [[resolved-front][Enable Systemd Resolved]].
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml :noweb yes
 <<enable-resolved>>
 #+END_SRC
@@ -5200,7 +5200,7 @@ service units on boot.  See [[resolved-front][Enable Systemd Resolved]].
 Campus machines use the campus name server on Core (or ~dns.google~),
 and include the institute's private domain in their search lists.
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml
 
 - name: Configure resolved.
@@ -5218,7 +5218,7 @@ and include the institute's private domain in their search lists.
   - Restart Systemd resolved.
 #+END_SRC
 
-#+CAPTION: =roles_t/campus/handlers/main.yml=
+#+CAPTION: [[file:roles_t/campus/handlers/main.yml][=roles_t/campus/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/handlers/main.yml
 
 - name: Reload Systemd.
@@ -5238,7 +5238,7 @@ The institute uses a common time reference throughout the campus.
 This is essential to campus security, improving the accuracy of log
 and file timestamps.
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml
 
 - name: Configure timesyncd.
@@ -5249,7 +5249,7 @@ and file timestamps.
   notify: Restart systemd-timesyncd.
 #+END_SRC
 
-#+CAPTION: =roles_t/campus/handlers/main.yml=
+#+CAPTION: [[file:roles_t/campus/handlers/main.yml][=roles_t/campus/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/handlers/main.yml
 
 - name: Restart systemd-timesyncd.
@@ -5265,7 +5265,7 @@ The administrator often needs to read (directories of) log files owned
 by groups ~root~ and ~adm~.  Adding the administrator's account to
 these groups speeds up debugging.
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml
 
 - name: Add {{ ansible_user }} to system groups.
@@ -5283,7 +5283,7 @@ trustworthy, so its certificate is added to the host's set of trusted
 CAs.  (For more information about how the small institute manages its
 keys, certificates and passwords, see [[*Keys][Keys]].)
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml
 
 - name: Trust the institute CA.
@@ -5297,7 +5297,7 @@ keys, certificates and passwords, see [[*Keys][Keys]].)
   notify: Update CAs.
 #+END_SRC
 
-#+CAPTION: =roles_t/campus/handlers/main.yml=
+#+CAPTION: [[file:roles_t/campus/handlers/main.yml][=roles_t/campus/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/handlers/main.yml
 
 - name: Update CAs.
@@ -5309,7 +5309,7 @@ keys, certificates and passwords, see [[*Keys][Keys]].)
 
 The institute prefers to install security updates as soon as possible.
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml
 
 - name: Install basic software.
@@ -5329,7 +5329,7 @@ tasks below.
 - General type of mail configuration: Internet Site
 - System mail name: new.small.private
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml :noweb yes
 
 - name: Install Postfix.
@@ -5363,7 +5363,7 @@ tasks below.
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/campus/handlers/main.yml=
+#+CAPTION: [[file:roles_t/campus/handlers/main.yml][=roles_t/campus/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/handlers/main.yml
 
 - name: Restart Postfix.
@@ -5379,7 +5379,7 @@ For the edification of programs consulting the =/etc/hosts= file, the
 institute's domain name and public IP address are added.  The Debian
 custom of translating the host name into ~127.0.1.1~ is also followed.
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml
 
 - name: Hard-wire important IP addresses.
@@ -5405,7 +5405,7 @@ server so that the NAGIOS4 server on Core can collect statistics.  The
 NAGIOS service is discussed in the [[*Configure NRPE][Configure NRPE]] section of [[*The Core Role][The Core
 Role]].
 
-#+CAPTION: =roles_t/campus/tasks/main.yml=
+#+CAPTION: [[file:roles_t/campus/tasks/main.yml][=roles_t/campus/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/tasks/main.yml
 
 - name: Install NRPE.
@@ -5443,7 +5443,7 @@ Role]].
     state: started
 #+END_SRC
 
-#+CAPTION: =roles_t/campus/handlers/main.yml=
+#+CAPTION: [[file:roles_t/campus/handlers/main.yml][=roles_t/campus/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/campus/handlers/main.yml
 
 - name: Reload NRPE server.
@@ -5457,16 +5457,16 @@ Role]].
 * The Ansible Configuration
 
 The small institute uses Ansible to maintain the configuration of its
-servers.  The administrator keeps an Ansible inventory in =hosts=, and
-runs the playbook =site.yml= to apply the appropriate institutional
+servers.  The administrator keeps an Ansible inventory in [[file:hosts][=hosts=]], and
+runs playbook [[file:playbook/site.yml][=site.yml=]] to apply the appropriate institutional
 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 [[*Testing][Testing]].
 
 The /actual/ Ansible configuration is kept in a Git "superproject"
-containing replacements for the example =hosts= inventory and
-=site.yml= playbook, as well as the =public/= and =private/=
+containing replacements for the example [[file:hosts][=hosts=]] inventory and
+[[file:playbooks/site.yml][=site.yml=]] playbook, as well as the [[file:public/][=public/=]] and [[file:private/][=private/=]]
 particulars.  Thus changes to this document and its tangle are easily
 merged with ~git pull --recurse-submodules~ or ~git submodule update~,
 while changes to the institute's particulars are committed to a
@@ -5474,7 +5474,7 @@ separate revision history.
 
 ** =ansible.cfg=
 
-The Ansible configuration file =ansible.cfg= contains just a handful
+The Ansible configuration file [[file:ansible.cfg][=ansible.cfg=]] contains just a handful
 of settings, some included just to create a test jig as described in
 [[*Testing][Testing]].
 
@@ -5482,14 +5482,14 @@ of settings, some included just to create a test jig as described in
   "automatic interpreter discovery" (described [[https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html][here]]).  It declares
   that Python 3 can be expected on all institute hosts.
 - ~vault_password_file~ is set to suppress prompts for the vault
-  password.  The institute keeps its vault password in =Secret/= (as
+  password.  The institute keeps its vault password in [[file:Secret/][=Secret/=]] (as
   described in [[*Keys][Keys]]) and thus sets this parameter to
-  =Secret/vault-password=.
+  [[file:Secret/vault-password][=Secret/vault-password=]].
 - ~inventory~ is set to avoid specifying it on the command line.
 - ~roles_path~ is set to the recently tangled roles files in
-  =roles_t/= which are preferred in the test configuration.
+  [[file:roles_t/][=roles_t/=]] which are preferred in the test configuration.
 
-#+CAPTION: =ansible.cfg=
+#+CAPTION: [[file:ansible.cfg][=ansible.cfg=]]
 #+BEGIN_SRC conf :tangle ansible.cfg
 [defaults]
 interpreter_python=/usr/bin/python3
@@ -5500,7 +5500,7 @@ roles_path=roles_t
 
 ** =hosts=
 
-The Ansible inventory file =hosts= describes all of the institute's
+The Ansible inventory file [[file:hosts][=hosts=]] describes all of the institute's
 machines starting with the main servers Front, Core and Gate.  It
 provides the IP addresses, administrator account names and passwords
 for each machine.  The IP addresses are all private, campus network
@@ -5508,7 +5508,7 @@ addresses except Front's public IP.  The following example host file
 describes three test servers named ~front~, ~core~ and ~gate~.
 
 #+NAME: hosts
-#+CAPTION: =hosts=
+#+CAPTION: [[file:hosts][=hosts=]]
 #+BEGIN_SRC conf :tangle hosts
 all:
   vars:
@@ -5531,11 +5531,11 @@ all:
 #+END_SRC
 
 The values of the ~ansible_become_password~ key are references to
-variables defined in =Secret/become.yml=, which is loaded as
+variables defined in [[file:Secret/become.yml][=Secret/become.yml=]], which is loaded as
 "extra" variables by a ~-e~ option on the ~ansible-playbook~ command
 line.
 
-#+CAPTION: =Secret/become.yml=
+#+CAPTION: [[file:Secret/become.yml][=Secret/become.yml=]]
 #+BEGIN_SRC conf :tangle Secret/become.yml :tangle-mode u=rw
 become_front: !vault |
         $ANSIBLE_VAULT;1.1;AES256
@@ -5566,16 +5566,16 @@ become_gate: !vault |
 The passwords are individually encrypted just to make it difficult to
 acquire a list of all institute privileged account passwords in one
 glance.  The multi-line values are generated by the ~ansible-vault
-encrypt_string~ command, which uses the =ansible.cfg= file and thus
-the =Secret/vault-password= file.
+encrypt_string~ command, which uses the [[file:ansible.cfg][=ansible.cfg=]] file and thus
+the [[file:Secret/vault-password][=Secret/vault-password=]] file.
 
 ** =playbooks/site.yml=
 
-The example =playbooks/site.yml= playbook (below) applies the
+The example [[file:playbooks/site.yml][=playbooks/site.yml=]] playbook (below) applies the
 appropriate institutional role(s) to the hosts and groups defined in
-the example inventory: =hosts=.
+the example inventory: [[file:hosts][=hosts=]].
 
-#+CAPTION: =playbooks/site.yml=
+#+CAPTION: [[file:playbooks/site.yml][=playbooks/site.yml=]]
 #+BEGIN_SRC conf :tangle playbooks/site.yml :mkdirp yes
 ---
 - name: Configure Front
@@ -5599,13 +5599,13 @@ the example inventory: =hosts=.
 
 As already mentioned, the small institute keeps its Ansible vault
 password, a "master secret", on the encrypted partition mounted at
-=Secret/= in a file named =vault-password=.  The administrator
+[[file:Secret/][=Secret/=]] in a file named =vault-password=.  The administrator
 generated a 16 character pronounceable password with ~gpw 1 16~ and
 saved it like so: ~gpw 1 16 >Secret/vault-password~.  The following
 example password matches the example encryptions above.
 
 #+NAME: vault-password
-#+CAPTION: =Secret/vault-password=
+#+CAPTION: [[file:Secret/vault-password][=Secret/vault-password=]]
 #+BEGIN_SRC conf :tangle Secret/vault-password :tangle-mode u=r :mkdirp yes
 alitysortstagess
 #+END_SRC
@@ -5677,9 +5677,9 @@ super-project's directory.
 
 ** Maintaining A Working Ansible Configuration
 
-The Ansible roles currently tangle into the =roles_t/= directory to
+The Ansible roles currently tangle into the [[file:roles_t/][=roles_t/=]] directory to
 ensure that debugged Ansible code in =roles/= is not clobbered by code
-tangled from this document.  Comparing =roles_t/= with =roles/= will
+tangled from this document.  Comparing [[file:roles_t/][=roles_t/=]] with =roles/= will
 reveal any changes made to =roles/= during debugging that need to be
 reconciled with this document /as well as/ any policy changes in this
 document that require changes to the current =roles/=.
@@ -5699,14 +5699,14 @@ to get their defaults from =./ansible.cfg=.
 
 ** Sub-command Blocks
 
-The code blocks in this chapter tangle into the =inst= script.  Each
+The code blocks in this chapter tangle into the [[file:inst][=inst=]] script.  Each
 block examines the script's command line arguments to determine
 whether its sub-command was intended to run, and exits with an
 appropriate code when it is done.
 
 The first code block is the header of the ~./inst~ script.
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst :tangle-mode u=rwx,g=rx
 #!/usr/bin/perl -w
 #
@@ -5721,11 +5721,11 @@ use IO::File;
 The next code block does not implement a sub-command; it implements
 part of /all/ ~./inst~ sub-commands.  It performs a "sanity check" on
 the current directory, warning of missing files or directories, and
-especially checking that all files in =private/= have appropriate
-permissions.  It probes past the =Secret/= mount point (probing for
-=Secret/become.yml=) to ensure the volume is mounted.
+especially checking that all files in [[file:private/][=private/=]] have appropriate
+permissions.  It probes past the [[file:Secret/][=Secret/=]] mount point (probing for
+[[file:Secret/become.yml][=Secret/become.yml=]]) to ensure the volume is mounted.
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst
 
 sub note_missing_file_p ($);
@@ -5777,12 +5777,12 @@ sub note_missing_directory_p ($) {
 
 To ensure that Ansible and ~./inst~ are sympatico vis-a-vi certain
 variable values (esp. private values like network addresses), a
-=check-inst-vars.yml= playbook is used to update the Perl syntax file
-=private/vars.pl= before ~./inst~ loads it.  The Perl code in =inst=
-declares the necessary global variables and =private/vars.pl= sets
+[[file:playbooks/check-inst-vars.yml][=check-inst-vars.yml=]] playbook is used to update the Perl syntax file
+[[file:private/vars.pl][=private/vars.pl=]] before ~./inst~ loads it.  The Perl code in [[file:inst][=inst=]]
+declares the necessary global variables and [[file:private/vars.pl][=private/vars.pl=]] sets
 them.
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC conf :tangle inst
 
 sub mysystem (@) {
@@ -5798,9 +5798,9 @@ our ($domain_name, $domain_priv, $front_addr, $gate_wifi_addr);
 do "./private/vars.pl";
 #+END_SRC
 
-The playbook that updates =private/vars.pl=:
+The playbook that updates [[file:private/vars.pl][=private/vars.pl=]]:
 
-#+CAPTION: =playbooks/check-inst-vars.yml=
+#+CAPTION: [[file:playbooks/check-inst-vars.yml][=playbooks/check-inst-vars.yml=]]
 #+BEGIN_SRC conf :tangle playbooks/check-inst-vars.yml
 - hosts: localhost
   gather_facts: no
@@ -5820,25 +5820,25 @@ The playbook that updates =private/vars.pl=:
 ** The CA Command
 
 The next code block implements the ~CA~ sub-command, which creates a
-new CA (certificate authority) in =Secret/CA/= as well as SSH and PGP
+new CA (certificate authority) in [[file:Secret/CA/][=Secret/CA/=]] as well as SSH and PGP
 keys for the administrator, Monkey, Front and ~root~, also in
-sub-directories of =Secret/=.  The CA is created with the "common
+sub-directories of [[file:Secret/][=Secret/=]].  The CA is created with the "common
 name" provided by the ~full_name~ variable.  An example is given
 here.
 
-#+CAPTION: =public/vars.yml=
+#+CAPTION: [[file:public/vars.yml][=public/vars.yml=]]
 #+BEGIN_SRC conf :tangle public/vars.yml
 full_name: Small Institute LLC
 #+END_SRC
 
-The =Secret/= directory is on an off-line, encrypted volume plugged in
-just for the duration of ~./inst~ commands, so =Secret/= is actually a
+The [[file:Secret/][=Secret/=]] directory is on an off-line, encrypted volume plugged in
+just for the duration of ~./inst~ commands, so [[file:Secret/][=Secret/=]] is actually a
 symbolic link to a volume's automount location.
 
 : ln -s /media/sysadm/ADE7-F866/ Secret
 
-The =Secret/CA/= directory is prepared using Easy RSA's ~make-cadir~
-command.  The =Secret/CA/vars= file thus created is edited to contain
+The [[file:Secret/CA/][=Secret/CA/=]] directory is prepared using Easy RSA's ~make-cadir~
+command.  The [[file:Secret/CA/vars][=Secret/CA/vars=]] file thus created is edited to contain
 the appropriate names (or just to set ~EASYRSA_DN~ to ~cn_only~).
 
 : sudo apt install easy-rsa
@@ -5852,7 +5852,7 @@ LLC~.  The CA is used to issue certificates for ~front~, ~gate~ and
 ~core~, which are installed on the servers during the next ~./inst
 config~.
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst
 
 if (defined $ARGV[0] && $ARGV[0] eq "CA") {
@@ -5871,8 +5871,8 @@ if (defined $ARGV[0] && $ARGV[0] eq "CA") {
   mysystem "cd Secret/CA; ./easyrsa build-server-full core.$pvt nopass";
   mysystem "cd Secret/CA; ./easyrsa build-client-full core nopass";
   umask 077;
-  mysystem "openvpn --genkey --secret Secret/front-ta.key";
-  mysystem "openvpn --genkey --secret Secret/gate-ta.key";
+  mysystem "openvpn --genkey secret Secret/front-ta.key";
+  mysystem "openvpn --genkey secret Secret/gate-ta.key";
   mysystem "openssl dhparam -out Secret/front-dh2048.pem 2048";
   mysystem "openssl dhparam -out Secret/gate-dh2048.pem 2048";
 
@@ -5923,7 +5923,7 @@ Example command lines:
 : ./inst config HOST
 : ./inst config -n HOST
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst
 
 if (defined $ARGV[0] && $ARGV[0] eq "config") {
@@ -6001,12 +6001,12 @@ revoked:
 #+END_SRC
 
 The test campus starts with the empty membership roll found in
-=private/members-empty.yml= and saved in =private/members.yml=
+[[file:private/members-empty.yml][=private/members-empty.yml=]] and saved in =private/members.yml=
 (which is /not/ tangled from this document, thus /not/ over-written
 during testing).  If =members.yml= is not found, =members-empty.yml=
 is used instead.
 
-#+CAPTION: =private/members-empty.yml=
+#+CAPTION: [[file:private/members-empty.yml][=private/members-empty.yml=]]
 #+BEGIN_SRC conf :tangle private/members-empty.yml :tangle-mode u=rw
 ---
 members:
@@ -6017,7 +6017,7 @@ revoked: []
 Both locations go on the ~membership_rolls~ variable used by the
 ~include_vars~ tasks.
 
-#+CAPTION: =private/vars.yml=
+#+CAPTION: [[file:private/vars.yml][=private/vars.yml=]]
 #+BEGIN_SRC conf :tangle private/vars.yml
 membership_rolls:
 - "../private/members.yml"
@@ -6028,7 +6028,7 @@ Using the standard Perl library ~YAML::XS~, the subroutine for
 reading the membership roll is simple, returning the top-level hash
 read from the file.  The dump subroutine is another story (below).
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst
 
 use YAML::XS qw(LoadFile DumpFile);
@@ -6102,7 +6102,7 @@ produced by the for-the-purpose printer makes the resulting membership
 roll easier to read, with the ~username~ and ~status~ at the top of
 each record.
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst
 
 sub print_member ($$) {
@@ -6144,13 +6144,13 @@ sub print_member ($$) {
 The next code block implements the ~new~ sub-command.  It adds a new
 member to the institute's membership roll.  It runs an Ansible
 playbook to create the member's Nextcloud user, updates
-=private/members.yml=, and runs the =site.yml= playbook.  The site
+=private/members.yml=, and runs the [[file:playbooks/site.yml][=site.yml=]] playbook.  The site
 playbook (re)creates the member's accounts on Core and Front,
 (re)installs the member's personal homepage on Front, and the member's
 Fetchmail service on Core.  All services are configured with an
 initial, generated password.
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst
 
 sub valid_username (@);
@@ -6213,7 +6213,7 @@ sub strip_vault ($) {
 }
 #+END_SRC
 
-#+CAPTION: =playbooks/nextcloud-new.yml=
+#+CAPTION: [[file:playbooks/nextcloud-new.yml][=playbooks/nextcloud-new.yml=]]
 #+BEGIN_SRC conf :tangle playbooks/nextcloud-new.yml
 - hosts: core
   no_log: yes
@@ -6249,7 +6249,7 @@ pass~ command.  In either case, the administrator needs to update the
 membership roll, and so receives an encrypted email, which gets piped
 into ~./inst pass~.  This command decrypts the message, parses the
 (YAML) content, updates =private/members.yml=, and runs the full
-Ansible =site.yml= playbook to update the servers.  If all goes well a
+Ansible [[file:playbooks/site.yml][=site.yml=]] playbook to update the servers.  If all goes well a
 message is sent to ~member@core~.
 
 *** Less Aggressive passwd.
@@ -6261,9 +6261,9 @@ update the servers, so it does not need an SSH key and password to
 (nor equivalent) on Core.  It /is/ a set-UID ~shadow~ script so it can
 read =/etc/shadow=.  The member will need to wait for confirmation
 from the administrator, but /all/ keys to ~root~ at the institute stay
-in =Secret/=.
+in [[file:Secret/][=Secret/=]].
 
-#+CAPTION: =roles_t/core/templates/passwd=
+#+CAPTION: [[file:roles_t/core/templates/passwd][=roles_t/core/templates/passwd=]]
 #+BEGIN_SRC perl :tangle roles_t/core/templates/passwd :mkdirp yes
 #!/bin/perl -wT
 
@@ -6349,9 +6349,9 @@ exit;
 
 The following code block implements the ~./inst pass~ command, used by
 the administrator to update =private/members.yml= before running
-=playbooks/site.yml= and emailing the concerned member.
+[[file:playbooks/site.yml][=playbooks/site.yml=]] and emailing the concerned member.
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst
 
 use MIME::Base64;
@@ -6408,7 +6408,7 @@ As always: please email root with any questions or concerns.\n";
 And here is the playbook that interacts with Nextcloud's ~occ
 users:resetpassword~ command using ~expect(1)~.
 
-#+CAPTION: =playbooks/nextcloud-pass.yml=
+#+CAPTION: [[file:playbooks/nextcloud-pass.yml][=playbooks/nextcloud-pass.yml=]]
 #+BEGIN_SRC conf :tangle playbooks/nextcloud-pass.yml
 - hosts: core
   no_log: yes
@@ -6447,7 +6447,7 @@ admin user is added to the shadow group so that the script can read
 key for ~root@core~ is also imported into the admin user's GnuPG
 configuration so that the email to root can be encrypted.
 
-#+CAPTION: =roles_t/core/tasks/main.yml=
+#+CAPTION: [[file:roles_t/core/tasks/main.yml][=roles_t/core/tasks/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/tasks/main.yml
 
 - name: Install institute passwd command.
@@ -6493,7 +6493,7 @@ configuration so that the email to root can be encrypted.
   notify: Import root PGP key.
 #+END_SRC
 
-#+CAPTION: =roles_t/core/handlers/main.yml=
+#+CAPTION: [[file:roles_t/core/handlers/main.yml][=roles_t/core/handlers/main.yml=]]
 #+BEGIN_SRC conf :tangle roles_t/core/handlers/main.yml
 
 - name: Import root PGP key.
@@ -6505,7 +6505,7 @@ configuration so that the email to root can be encrypted.
 
 The ~old~ command disables a member's accounts and clients.
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst
 
 if (defined $ARGV[0] && $ARGV[0] eq "old") {
@@ -6527,7 +6527,7 @@ if (defined $ARGV[0] && $ARGV[0] eq "old") {
 }
 #+END_SRC
 
-#+CAPTION: =playbooks/nextcloud-old.yml=
+#+CAPTION: [[file:playbooks/nextcloud-old.yml][=playbooks/nextcloud-old.yml=]]
 #+BEGIN_SRC conf :tangle playbooks/nextcloud-old.yml
 - hosts: core
   tasks:
@@ -6547,7 +6547,7 @@ if (defined $ARGV[0] && $ARGV[0] eq "old") {
 
 The ~client~ command creates an OpenVPN configuration (=.ovpn=) file
 authorizing wireless devices to connect to the institute's VPNs.  The
-command uses the EasyRSA CA in =Secret/=.  The generated configuration
+command uses the EasyRSA CA in [[file:Secret/][=Secret/=]].  The generated configuration
 is slightly different depending on the type of host, given as the
 first argument to the command.
 
@@ -6589,7 +6589,7 @@ up /etc/openvpn/update-systemd-resolved
 up-restart
 #+END_SRC
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst :noweb yes
 sub write_template ($$$$$$$$$);
 sub read_file ($);
@@ -6702,11 +6702,11 @@ sub read_file ($) {
 
 ** Institute Command Help
 
-This should be the last block tangled into the =inst= script.  It
+This should be the last block tangled into the [[file:inst][=inst=]] script.  It
 catches any command lines that were not handled by a sub-command
 above.
 
-#+CAPTION: =inst=
+#+CAPTION: [[file:inst][=inst=]]
 #+BEGIN_SRC perl :tangle inst
 
 die "usage: $0 [CA|config|new|pass|old|client] ...\n";
@@ -6715,10 +6715,10 @@ die "usage: $0 [CA|config|new|pass|old|client] ...\n";
 
 * Testing
 
-The example files in this document, =ansible.cfg= and =hosts= as
-well as those in =public/= and =private/=, along with the
+The example files in this document, [[file:ansible.cfg][=ansible.cfg=]] and [[file:hosts][=hosts=]] as
+well as those in [[file:public/][=public/=]] and [[file:private/][=private/=]], along with the
 matching EasyRSA certificate authority and GnuPG key-ring in
-=Secret/= (included in the distribution), can be used to configure
+[[file:Secret/][=Secret/=]] (included in the distribution), can be used to configure
 three VirtualBox VMs simulating Core, Gate and Front in a test network
 simulating a campus Ethernet, campus ISP, and commercial cloud.  With
 the test network up and running, a simulated member's notebook can be
@@ -7000,7 +7000,7 @@ VBoxManage modifyvm gate --hostonlyadapter3 vboxnet1
 #+END_SRC
 
 Before rebooting, the MAC addresses of the three network interfaces
-should be compared to the example variable settings in =hosts=.  The
+should be compared to the example variable settings in [[file:hosts][=hosts=]].  The
 values of the ~gate_lan_mac~, ~gate_wifi_mac~, and ~gate_isp_mac~
 variables /must/ agree with the MAC addresses assigned to the virtual
 machine's network interfaces.  The following table assumes device
@@ -7269,7 +7269,7 @@ host www
 
 ** Test Web Pages
 
-Next, the administrator copies =Backup/WWW/= (included in the
+Next, the administrator copies [[file:Backup/WWW/][=Backup/WWW/=]] (included in the
 distribution) to =/WWW/= on ~core~ and sets the file permissions
 appropriately.
 
@@ -7552,11 +7552,11 @@ complete.  Additional tests are needed.
 
 The ~backup~ command has not been tested.  It needs an encrypted
 partition with which to sync?  And then some way to compare that to
-=Backup/=?
+[[file:Backup/][=Backup/=]]?
 
 *** Restore
 
-The restore process has not been tested.  It might just copy =Backup/=
+The restore process has not been tested.  It might just copy [[file:Backup/][=Backup/=]]
 to ~core:/~, but then it probably needs to fix up file ownerships,
 perhaps permissions too.  It could also use an example
 =Backup/Nextcloud/20220622.bak=.