Commit Graph

1151 Commits (717a8f90c69b627fae43bd7406cbb5c52de997d5)

Author SHA1 Message Date
Dustin fbbe86c651 r/unifi: Do not deploy exporter
The _unifi_exporter_ has been broken since several versions of UniFi
Network ago.
2025-03-16 16:40:57 -05:00
Dustin db54b03aa8 r/unifi: Switching to custom container image
The _linuxserver.io_ image for UniFi Network is deprecated.  It sucked
anyway.  I've created a simple image based on Debian that installs the
_unifi_ package from the upstream apt repository.  This image doesn't
require running anything as _root_, so it doesn't need a user namespace.
2025-03-16 16:40:57 -05:00
Dustin a423826fcd newvm: Add host to some groups by default
There are some groups that all hosts should belong to in almost all
cases.  Rather than have to remember to add the `--group` arguments for
each of these, the `newvm.sh` script will now enable them by default.
For hosts that should _not_ belong to (at least one of) these groups,
the `--no-default-groups` argument can be provided to suppress that
behavior.

The default groups, initially, are _chrony_ and _collectd_.
2025-03-16 16:37:19 -05:00
Dustin c300dc1b6c chrony: Add role/PB for chrony
I continually struggle with machines' (physical and virtual, even the
Roku devices!) clocks getting out of sync.  I have been putting off
fixing this because I wanted to set up a Windows-compatible NTP server
(i.e. on the domain controllers, with Kerberos signing), but there's
really no reason to wait for that to fix the clocks on all the
non-Windows machines, especially since there are exactly 0 Windows
machines on the network right now.

The *chrony* role and corresponding `chrony.yml` playbook are generic,
configured via the `chrony_pools`, `chrony_servers`, and `chrony_allow`
variables.  The values for these variables will configure the firewall
to act as an NTP server, synchronizing with the NTP pool on the
Internet, while all other machines will synchronize with it.  This
allows machines on networks without Internet access to keep their clocks
in sync.
2025-03-16 16:37:19 -05:00
Dustin 4ba5f2ced0 hosts.gw: Add gw1 to sudo group
The default privilege elevation tool is now `doas`, but _gw1_ uses
`sudo`.
2025-03-16 16:37:19 -05:00
Dustin e4a4944fbc postgresql: Add receipts/user DB
The Receipt application needs a PostgreSQL database on the central
server.
2025-03-16 14:47:30 -05:00
Dustin fd59f3ebb2 users: Do not create users on domain members
The point of the `users.yml` playbook is to manage static users for
machines that are not members of the AD domain.  Since this playbook is
included in `site.yml`, it gets applied to _all_ machines, even those
that _are_ (or will become) domain members.  Thus, we want to avoid
actually doing anything on those machines.
2025-02-25 21:03:59 -06:00
Dustin 5f4b1627db hosts: Add nut1.p.b to pyrocufflink group
*nut1.pyrocufflink.blue* is a member of the *pyrocufflink.blue* AD
domain.  I'm not sure how it got to be so without belonging to the
_pyrocufflink_ Ansible group...
2025-02-25 21:03:14 -06:00
Dustin 9ea8756610 inventory: Exclude test machines by default
We don't want Jenkins attemptying to manage test VMs.  I thought of
various ways to exclude them, but in the end, I think a simple name
match will work fine.

The host provisioner _should_ manage test VMs, though, so it will need
to be configured to set the `PYROCUFFLINK_EXCLUDE_TEST` environment
variable to `false` to override the default behavior.
2025-02-14 10:04:48 -06:00
Dustin 6ae3404b3a vmhost: Allow host provisioner to log in
This commit adds tasks to the `vmhost.yml` playbook to ensure the
*jenkins* user has the Host Provisioner's SSH key in its
`authorized_keys` file.  This allows the Host Provisioner to log in and
access the read-only _libvirt_ socket in order to construct the dynamic
Ansible inventory.
2025-02-08 16:49:14 -06:00
Dustin 757494b48b newvm: Use kickstart from Gitea by default
The canonical location for kickstart scripts has _finally_ moved to
a real server instead of my desktop...
2025-02-08 16:49:14 -06:00
Dustin e7de5142f3 newvm: Allow setting cfgpol branch
The script that runs on first boot of a new machine that triggers
host provisioning can read the name of the configuration policy branch
to checkout from the QEMU firmware configuration option.  This commit
adds a `--cfg-branch` argument to `newvm.sh` that sets that value.  This
will be useful for testing new policy on a new VM.
2025-02-08 16:49:14 -06:00
Dustin 4d30798f54 newvm: Configure VM for dynamic inventory
This commit adds a new `--group` argument to the `newvm` script, which
adds the host to an Ansible group by listing it in the _libvirt_ domain
metadata.  Multiple groups can be specified by repeating the argument.
Additionally, the VM title is now always set to machine's FQDN, which
is what the dynamic inventory plugin uses to determine the inventory
hostname.

The dynamic inventory plugin parses the _libvirt_ domain metadata and
extracts group membership from the `<dch:groups>` XML element.  Each
`<dch:group>` sub-element specifies a group to which the host belongs.

Unfortunately, `virt-install` does not support modifying the
`<metadata>` element in the _libvirt_ domain XML document, so we have
to resort to using `virsh`.  To ensure the metadata are set before the
guest OS boots and tries to access them, we fork and run `virsh` in
a separate process.
2025-02-08 15:35:34 -06:00
Dustin 7ff18ab75e Introduce dynamic inventory
In order to fully automate host provisioning, we need to eliminate the
manual step of adding hosts to the Ansible inventory.  Ansible has had
the _community.libvirt.libvirt_ inventory plugin for quite a while, but
by itself it is insufficient, as it has no way to add hosts to groups
dynamically.  It does expose the domain XML, but parsing that and
extracting group memberships from that using Jinja templates would be
pretty terrible.  Thus, I decided the easiest and most appropriate
option would be to develop my own dynamic inventory plugin.

* Supports multiple _libvirt_ servers
* Can connect to the read-only _libvirt_ socket
* Can optionally exclude VMs that are powered off
* Can exclude VMs based on their operating system (if the _libosinfo_
  metadata is specified in the domain metadata)
* Can add hosts to groups as specified in the domain metadata
* Exposes guest info as inventory host variables (requires QEMU guest
  agent running in the VM and does not work with a read-only _libvirt_
  connection)
2025-02-08 15:29:58 -06:00
Dustin e9d6020563 all: Set root authorized keys
The `root_authorized_keys` variable was originally defined only for the
*pyrocufflink* group.  This used to effectively be "all" machines, since
everything was a member of the AD domain.  Now that we're moving away
from that deployment model, we still want to have the break-glass
option, so we need to define the authorized keys for the _all_ group.
2025-02-08 15:29:57 -06:00
Dustin d916545e29 synapse: Remove group variables
This was the last group that had an entire file encrypted with Ansible
Vault.  Now that the Synapse server is long gone, rather than convert it
to having individually-encrypted values, we can get rid of it entirely.
2025-02-08 15:29:57 -06:00
Dustin 8bd0722422 pyrocufflink: Remove root password
While having a password set for _root_ provides a convenient way of
accessing a machine even if it is not available via SSH, using a static
password in this way is quite insecure and not worth the risk.  I may
try to come up with a better way to set a unique password for each
machine eventually, but for now, having this password here is too
dangerous to keep.
2025-02-08 15:29:57 -06:00
Dustin 6f37f1417f site: Add master site playbook
The `site.yml` playbook imports all of the other playbooks, providing a
way to deploy _everything_.  Normally, this would only be done for a
single host, as part of its initial provisioning, to quickly apply all
common configuration and any application-specific configuration for
whatever roles the host happens to hold.

The `host-setup.yml` playbook provides an entry point for configuring
all common configuration.  Basically anything we want to do to _every_
machine, regardless of its location or role.
2025-02-08 15:29:55 -06:00
Dustin 143c96074e callbacks: Enable ARA
[ARA Records Ansible][0] is a web-based reporting tool for Ansible.  It
consists of a callback plugin that submits task/playbook results to an
HTTP API and a browser GUI to display them.

[0]: https://ara.recordsansible.org/
2025-02-01 17:36:58 -06:00
Dustin 164d86d646 r/postgresql-data: Manage users and databases
This role can ensure PostgreSQL users and databases are created for
applications that are not themselves managed by Ansible.  Notably, we
need to do this for anything deployed in Kubernetes that uses the
central database server.
2025-02-01 17:36:58 -06:00
Dustin 34c1256f27 base: Factor out SSH host, user cert roles
Moving the SSH host and user certificate configuration roles out of
`base.yml` into their own playbooks.  This will make it easier to deploy
them separately, and target different sets of hosts.  The main driver
for this change is the OVH VPS; being external, it cannot communicate
with SSHCA and thus cannot have a signed host certificate.  As such, we
do not want to try to configure the SSHCA client on it at all.
2025-02-01 17:36:58 -06:00
Dustin a3a2dde6ab callbacks: Add ntfy callback plugin
This plugin sends a notification using _ntfy_ whenever a playbook
fails.  This will be useful especially for automated deployments when
the playbook was not launched manually.
2025-02-01 17:36:58 -06:00
Dustin f705e98fab hosts: Add k8s-iot-net-ctrl group
The *k8s-iot-net-ctrl* group is for the Raspberry Pi that has the Zigbee
and Z-Wave controllers connected to it.  This node runs the Zigbee2MQTT
and ZWaveJS2MQTT servers as Kubernetes pods.
2025-01-31 19:49:51 -06:00
Dustin b1c29fc12a hosts: Remove hostvds group
Since the _hostvds_ group is not defined in the static inventory but by
the OpenStack inventory plugin via `hostvds.openstack.yml`, when the
static inventory is used by itself, Ansible fails to load it with an
error:

> Section [vps:children] includes undefined group: hostvds

To fix this, we could explicitly define an empty _hostvds_ group in the
static inventory, but since we aren't currently running any HostVDS
instances, we might as well just get rid of it.
2025-01-31 19:45:58 -06:00
Dustin 878a099752 r/kubelet: Ensure iscsi service is running
The _iscsi.socket_ unit gets enabled by default with the
_iscsi-initiator-utils_ package is installed, but it won't start
automatically until the next boot.  Without this service running,
Longhorn volumes will not be able to attach to the node, so we need to
explicitly ensure it is running before any workloads are assigned to the
node.
2025-01-31 19:01:27 -06:00
Dustin a9a6a30e59 r/{cri-o,kubelet}: Support versioned packages
Fedora 41 introduced versioned package names for Kubernetes components,
including CRI-O.  The intent is to allow multiple versions of Kubernetes
to be available (but not necessarily installed) within a given Fedora
release.  In order to use these packages, we need to set the desired
Kubernetes version, via the new `kubernetes_version` Ansible variable.
2025-01-31 18:57:21 -06:00
Dustin cbc4d29bd6 r/base: Install python3-libdnf5
Fedora 41 uses _dnf5_ by default.  Being written in C, its Python API is
an optional feature that needs to be installed separately.
2025-01-31 18:55:58 -06:00
Dustin ec4fa25bd8 Merge remote-tracking branch 'refs/remotes/origin/master' 2025-01-30 21:15:40 -06:00
Dustin a58dbb74c5 r/vmhost: Clean up qemu packages
At some point, the _qemu-kvm_ package became a meta-package that
installs _everything_ QEMU-related.  All drivers, backends, frontends,
etc. get pulled in, which results in a huge amount of wasted space.
Recently, the VM hosts started getting alerts about their `/` filesystem
getting too full, which is how I discovered this.

We can dramatically reduce the disk space footprint by installing only
the "core" package and the drivers we need for our servers.

After making and applying this change, which marks the listed packages
as "leaf" installs, I then manually uninstalled the _qemu-kvm_ package.
This uninstalled everything else that is not specifically listed.
2025-01-28 17:36:35 -06:00
Dustin 272e89d65a Merge remote-tracking branch 'refs/remotes/origin/master' 2025-01-28 17:34:37 -06:00
Dustin c00d6f49de hosts: Add OVH VPS
It turns out, $0.99/mo might be _too_ cheap for a cloud server.  Running
the Blackbox Exporter+vmagent on the HostVDS instance worked for a few
days, but then it started having frequent timeouts when probing the
websites.  I tried redeploying the instance, switching to a larger
instance, and moving it to different networks.  Unfortunately, none of
this seemed to help.

Switching over to a VPS running in OVH cloud.  OVH VPS servers are
managed statically, as opposed to via API, so we can't use Pulumi to
create them.  This one was created for me when I signed up for an OVH
acount.
2025-01-26 13:08:59 -06:00
Dustin 33f315334e users: Configure sudo on some machines
`doas` is not available on Alma Linux, so we still have to use `sudo` on
the VPS.
2025-01-26 13:08:59 -06:00
Dustin 319cc80a9f inventory: Configure for HostVDS openstack
Using the Ansible OpenStack inventory plugin, we can automatically fetch
information about running instances in HostVDS.  We're deriving group
membership from the `groups` metadata tag.

The OpenStack API password must be specified in a `secure.yaml` file.
We're omitting this from the repository because there's no apparent way
to encrypt it.

The inventory plugin tends to prefer IPv6 addresses over IPv4 when
populating `ansible_host`, even if the control machine does not have
IPv6 connectivity.  Thus, we have to compose the relevant variables
ourselves with a Jinja2 expression.
2025-01-26 13:08:59 -06:00
Dustin f868cea05c pulumi: Manage HostVDS instances
HostVDS provides public access to their OpenStack API, which we can use
to manage cloud instances.  This particular instance will be used to run
the remote blackbox exporter/vmagent to monitor website availability.
2025-01-26 13:08:59 -06:00
Dustin 304cacb95b dch-proxy: Proxy Victoria Metrics
Need to expose Victoria Metrics to the Internet so the `vmagent` process
on the VPS can push the metrics it has scraped from its Blackbox
exporter.  Authelia needs to allow access to the `/insert/` paths, of
course.
2025-01-26 13:08:59 -06:00
Dustin ad0bd7d4a5 remote-blackbox: Add group
The _remote-blackbox_ group defines a system that runs
_blackbox-exporter_ and _vmagent_ in a remote (cloud) location.  This
system will monitor our public web sites.  This will give a better idea
of their availability from the perspective of a user on the Internet,
which can be by factors that are necessarily visible from within the
network.
2025-01-26 13:08:59 -06:00
Dustin 3e8ac36f88 r/vmagent: Rework as container deployment
Like the _blackbox-exporter_ role, the _vmagent_ role now deploys
`vmagent` as a container.  This simplifies the process considerably,
eliminating the download/transfer step.

While refactoring this role, I also changed how the trusted CA
certificates are handled.  Rather than copy files, the role now expects
a `vmagent_ca_certs` variable.  This variable is a mapping of
certificate name (file name without extension) to PEM contents.  This
allows certificates to be defined using normal host/group variables.
2025-01-26 13:08:59 -06:00
Dustin dcf1e5adfc r/blackbox-exporter: Rework to run as container
Instead of downloading the `blackbox_exporter` binary from GitHub and
copying it to the managed node, the _blackbox-exporter_ role now
installs _podman_ and configures a systemd container unit (Quadlet) to
run it in a container.  This simplifies the deployment considerably, and
will make updating easier (just run the playbook with `-e
blackbox_exporter_pull_image=true`).
2025-01-26 13:06:54 -06:00
Dustin f5bee79bac hosts: Decommission bw0.p.b
Vaultwarden is now hosted in Kubernetes.
2025-01-10 20:09:53 -06:00
Dustin 3ebf91c524 dch-proxy: Update Vaultwarden backend
Vaultwarden is now hosted in Kubernetes.  The old
_bw0.pyrocufflink.blue_ will be decommissioned.
2025-01-10 20:03:35 -06:00
Dustin 81663a654d gw1/squid: Allow to Gitea kicstarts+from p.r
Since the canonical location for Anaconda kickstart scripts is now
Gitea, we need to allow hosts to access them from there.

Also allowing access from the _pyrocufflink.red_ network for e.g.
installation testing.
2024-12-27 13:07:11 -06:00
Dustin e51e933661 r/gitea: Serve kickstarts over HTTP
I want to use Gita as the canonical source for Anaconda kickstart
scripts.  There are certain situations, however, where they cannot be
accessed via HTTPS, such as on a Raspberry Pi without an RTC, since it
cannot validate the certificate without the correct time.  Thus, the
web server must not force an HTTPS redirect for these, but serve them
directly.
2024-12-27 10:51:00 -06:00
Dustin a00ffd10df r/jellyfin: Fix system.xml template whitespace
Jellyfin is one of those stupid programs that thinks it needs to mutate
its own config.  At startup, it apparently reads `system.xml` and then
writes it back out.  When it does this, it trims the final newline from
the file.  Then, the next time Ansible runs, the template rewrites the
file with the trailing newline, and thus determines that the file has
changed and restarts the service.  This cycle has been going on for a
while and is rather annoying.
2024-12-12 06:36:23 -06:00
Dustin 15cb675297 r/kubelet: Pass --config arg to service
The systemd unit configuration installed by Fedora's _kubeadm_ package
does not pass the `--config` argument to the kubelet service.  Without
this argument, the kubelet will not read the configuration file
generated by `kubeadm` from the `kubelet-config` ConfigMap.  Thus,
various features will not work correctly, including server TLS
bootstrap.
2024-12-07 09:35:57 -06:00
Dustin d2e8b9237f Enable doas become plugin for non AD members
The new servers that are not members of the AD domain use `doas` instead
of `sudo`.
2024-11-25 22:01:40 -06:00
Dustin bc7e7c2475 applyConfigPolicy: Configure SSH user certificate
In order to manage servers that are not members of the
_pyrocufflink.blue_ AD domain, Jenkins needs a user certificate signed
by the SSH CA.  Unfortunately, there is not really a good way to get a
certificate issued on demand in a non-interactive way, as SSHCA relies
on OIDC ID tokens which are issued by Authelia, and Authelica requires
browser-based interactive login and consent.  Until I can come up with a
better option, I've manually signed a certificate for Jenkins to use.

The Jenkins SSH Credentials plugin does not support certificates
directly, so in order to use one, we have to explicitly configure `ssh`
to load it via the `CertificateFile` option.
2024-11-25 21:17:44 -06:00
Dustin d993d59bee Deploy new Kubernetes nodes
The *stor-* nodes are dedicated to Longhorn replicas.  The other nodes
handle general workloads.
2024-11-24 10:33:21 -06:00
Dustin e41b6a619e newvm: Add domain argument
Now that we have multiple domains (_pyrocufflink.blue_ for AD domain
members and _pyrocufflink.black_ for the new machines), we need a way to
specify the domain for new machines when they are created.  Thus, the
`newvm.sh` script accepts either an FQDN or a `--domain` argument.  The
DHCP server will register the DNS name in the zone containing the
machine's domain name.
2024-11-24 10:33:21 -06:00
Dustin 7a5f01f8a3 r/doas: Configure sudo alternative
In the spirit of replacing bloated tools with unnecessary functionality
with smaller, more focused alternatives, we can use `doas` instead of
`sudo`.  Originally, it was a BSD tool, but the Linux port supports PAM,
so we can still use `pam_auth_ssh_agent` for ppasswordless
authentication.
2024-11-24 10:33:21 -06:00
Dustin c95a96a33c users: Manage static user accounts
The Samba AD domain performs two important functions: centralized user
identity mapping via LDAP, and centralized authentication via
Kerberos/GSSAPI.  Unfortunately, Samba, on both domain controllers and
members, is quite frustrating.  The client, _winbind_, frequently just
stops working and needs to have its cache flushed in order to resolve
user IDs again.  It also takes quite a lot of memory, something rather
precious on Raspberry Pis.  The DC is also somewhat flaky at times, and
cumbersome to upgrade.  In short, I really would like to get rid of as
much of it as possible.

For most use cases, OIDC can replace Kereros.  For SSH specifically, we
can use SSH certificates (which are issued to OIDC tokens).
Unfortunately, user and group accounts still need ID numbers assigned,
which is what _winbind_ does.  In reality, there's only one user that's
necessary: _dustin_.  It doesn't make sense to bring along all the
baggage of Samba just to map that one account.  Instead, it's a lot
simpler and more robust to create it statically.
2024-11-24 10:33:21 -06:00