Commit Graph

572 Commits (71b1363c586f9c519dfa843a55bc4a3c0ca0dc3a)

Author SHA1 Message Date
Dustin 71b1363c58 r/vmhost: Install nmap-ncat
While clients can use `virt-ssh-helper` to communicate with `libvirtd`,
they need `nc` in order to forward SPICE graphics communication.
2025-07-31 10:19:11 -05:00
Dustin 7f8e39ebd4 websites: chmod777.sh: Switch to mod_md for cert
The _chmod777.sh_ site now obtains its certificate from Let's
Encrypt using the Apache _mod_md_ (managed domain) module.  This
dramatically simplifies the deployment of this certificate, eliminating
the need for _cert-manager_ to obtain it, _cert-exporter_ to add it to
_certs.git_, and Jenkins to push it out to the web server.
2025-07-28 18:53:58 -05:00
Dustin 3270011fee r/vmhost: Work around libvirt SELinux policy bug
With the transition to modular _libvirt_ daemons, the SELinux policy is
a bit more granular.  Unfortunately, the new policy has a funny [bug]: it
assumes directories named `storage` under `/run/libvirt` must be for
_virtstoraged_ and labels them as such, which prevents _virtnetworkd_
from managing a virtual network named `storage`.

To work around this, we need to give `/run/libvirt/network` a special
label so that its children do not match the file transition pattern for
_virtstoraged_ and thus keep their `virtnetworkd_var_run_t` label.

[bug]: https://bugzilla.redhat.com/show_bug.cgi?id=2362040
2025-07-28 18:23:24 -05:00
Dustin 2ee86f6344 r/vmhost: Retry vm-autostart if libvirt is down
If the _libvirt_ daemon has not fully started by the time `vm-autostart`
runs, we want it to fail and try again shortly.  To allow this, we first
attempt to connect to the _libvirt_ socket, and if that fails, stop
immediately and try again in a second.  This way, the first few VMs
don't get skipped with the assumption that they're missing, just because
the daemon wasn't ready yet.
2025-07-28 18:20:50 -05:00
Dustin 4df047cf76 r/vmhost: Disable DynamicUsers for vm-autostart
_libvirt_ has gone full Polkit, which doesn't work with systemd dynamic
users.  So, we have to run `vm-autostart` as root (with no special
OS-level privileges) in order for Polkit to authorize the connection to
the daemon socket.
2025-07-28 18:18:35 -05:00
Dustin 59d17bf3f4 r/v-l: Use the host network
I don't know what the deal is, but restarting the _victoria-logs_
container makes it lose inbound network connectivity.  It appears that
the firewall rules that forward the ports to the container's namespace
seem to get lost, but I can't figure out why.  To fix it, I have to
flush the netfilter rules (`nft flush ruleset`) and then restart
_firewalld_ and _victoria-logs_ to recreate them.  This is rather
cumbersome, and since Victoria Logs runs on a dedicated VM, there's
really not much advantage to isolating the container's network.
2025-07-27 17:47:31 -05:00
Dustin dc924aa70b web/hlc: Remove obsolete form submit paths
Tabitha doesn't have any forms on her website any more.
2025-07-23 11:42:33 -05:00
Dustin 7034d5fec0 websites/tabitha: Redirect to HLC, use mod_md cert
Tabitha has effectively decommissioned her _tabitha.biz_ website.  She
wants it to redirect to the Hatch Learning Center site instead.
2025-07-23 11:40:25 -05:00
Dustin 48f47b8905 websites: apps.d.x: Switch to mod_md for cert
The _apps.du5t1n.xyz_ site now obtains its certificate from Let's
Encrypt using the Apache _mod_md_ (managed domain) module.  This
dramatically simplifies the deployment of this certificate, eliminating
the need for _cert-manager_ to obtain it, _cert-exporter_ to add it to
_certs.git_, and Jenkins to push it out to the web server.
2025-07-23 10:07:16 -05:00
Dustin 0eb6220672 r/mod_md: Configure Apache for ACME certificates
Apache supports fetching server certificates via ACME (e.g. from Let's
Encrypt) using a new module called _mod_md_.  Configuring the module is
fairly straightforward, mostly consisting of `MDomain` directives that
indicate what certificates to request.  Unfortunately, there is one
rather annoying quirk: the certificates it obtains are not immediately
available to use, and the server must be reloaded in order to start
using them.  Fortunately, the module provides a notification mechanism
via the `MDNotifyCmd` directive, which will run the specified command
after obtaining a certificate.  The command is executed with the
privileges of the web server, which does not have permission to reload
itself, so we have to build in some indirection in order to trigger the
reload: the notification runs a script that creates an empty file in the
server's state directory; systemd is watching for that file to be
created, then starts another service unit to trigger the actual reload,
then removes trigger file.

Website roles, etc. that want to switch to using _mod_md_ to manage
their certificates should depend on this role and add an `MDomain`
directive to their Apache configuration file fragments.
2025-07-23 10:07:16 -05:00
Dustin 9690234203 r/k8s-worker: Install iSCSI/NFS client tools
We don't want the iSCSI and NFS client tools to be installed on control
plane nodes.  Let's move this task to the _k8s-worker_ role so it will
only apply to worker nodes.
2025-07-22 16:21:49 -05:00
Dustin fb9f46cc47 r/haproxy: Do not start service
Since the _haproxy_ role relies on other roles to provide drop-in
configuration files for actual proxy configuration, we cannot start the
service in the base role.  If there are any issues with the drop-in
files that are added later, the service will not be able to start,
causing the playbook to fail and thus never be able to update the broken
configuration.  The dependent roles need to be responsible for starting
the service once they have put their configuration files in place.
2025-07-22 16:21:49 -05:00
Dustin c7374c8cca r/k8s-controller: Deploy HAProxy
The _haproxy_ role only installs HAProxy and provides some basic global
configuration; it expects another role to depend on it and provide
concrete proxy configuration with drop-in configuration files.  Thus, we
need a role specifically for the Kubernetes control plane nodes to
provide the configuration to proxy for the API server.
2025-07-22 16:21:49 -05:00
Dustin f62b11bb9d r/keepalived: Deploy keepalived
[keepalived][0] is a free implementation of the Virtual Router
Redundancy Protocol (VRRP), which is a simple method for automatically
assigning an IP address to one of several potential hosts based on
certain criteria.  It is particularly useful in conjunction with a load
balancer like HAProxy, to provide layer 3 redundancy in addition to
layer 7.  We will use it for both the reverse proxy for the public
websites and the Kubernetes API server.

[0]: https://www.keepalived.org/
2025-07-22 16:21:49 -05:00
Dustin ba3f61fb08 r/containers-image: Fix registries.conf path
`/etc/containers/registries.conf.d` is distinct from
`/etc/containers/registries.d`.  The latter contains YAML files relating
to image signatures, while the former contains TOML files relating to
registry locations.
2025-07-14 16:21:58 -05:00
Dustin e65bcc25ba r/k8s-worker: Fix typo in variable name
This typographical error was causing the "join" tasks to be executed
every time.
2025-07-14 16:18:35 -05:00
Dustin 61a4f64bbb r/nginx: Fix disabling access/error log files
It turns out _nginx_ has a built-in default value for `access_log` and
`error_log`, even if they are omitted from the configuration file.  To
actually disable writing logs to a file, we need to explicitly specify
`off`.
2025-07-14 16:11:35 -05:00
Dustin 906819dd1c r/apache: Use variables for HTTPS cert/key content
Using files for certificates and private keys is less than ideal.
The only way to "share" a certificate between multiple hosts is with
symbolic links, which means the configuration policy has to be prepared
for each managed system.  As we're moving toward a much more dynamic
environment, this becomes problematic; the host-provisioner will never
be able to copy a certificate to a new host that was just created.
Further, I have never really liked the idea of storing certificates and
private keys in Git anyway, even if it is in a submodule with limited
access.
2025-07-13 16:02:57 -05:00
Dustin f08f147931 r/pxe: Depend on apache role
Now that we're serving kickstart files from the PXE server, we need to
have a correctly-configured HTTPD server, with valid HTTPS certificates,
running there.
2025-07-13 16:02:57 -05:00
Dustin 6667066826 kubelet: Configure cri-o container registries
The _containers-image_ role configures _containers-registries.conf(5)_ and
_containers-cert.d(5)_, which are used by CRI-O (and `podman`).
Specifically, we'll use these to redirect requests for images on Docker
Hub (docker.io) to the internal caching proxy.
2025-07-12 16:45:47 -05:00
Dustin f8f3dd5f83 docker-proxy: Deploy a proxy/cache for Docker Hub
Docker Hub's rate limits are so low now that they've started to affect
my home lab.  Deploying a caching proxy and directing all pull requests
through it should prevent exceeding the limit.  It will also help
prevent containers from starting if access to the Internet is down, as
long as their images have been cached recently.
2025-07-12 16:45:47 -05:00
Dustin 6d1442faf0 r/lego-nginx: Configure LEGO for nginx
The *lego-nginx* role automates obtaining certificates for *nginx* via
ACME using `lego`.  It generates a shell script with the appropriate
arguments for `lego run`, runs it once to obtain a certificate
initially, then schedules it to run periodically via a systemd timer
unit. Using `lego`'s "hook" capability, the script signals the `nginx`
server process to reload.  This uses `doas` for now, but could be
adapted easily to use `sudo`, if the need ever arises.
2025-07-12 16:45:47 -05:00
Dustin a23bb1f043 r/pxe: Add directory for serving kickstarts
Now that kickstart scripts are generated from templates by a Jenkins
job, they need to be stored somewhere besides Gitea.  It makes sense to
serve them from the PXE server, since it's involved in the installation
process anyway (at least for physical machines).   Thus, we need a path
where the generated files can be uploaded by Jenkins and served by
Apache.
2025-07-12 16:12:23 -05:00
Dustin 4218137e1e r/minio-backups-cert: Fix nsupdate kinit for f42
The version of Samba in Fedora 42 has got some really weird bugs.  In
this case, it seems `net ads kerberos kinit -P` no longer works.  It
prints a vague `NT_STATUS_INTERNAL_ERROR` message, with no other
indication of what went wrong.  Fortunately, it's still possible to get
a ticket-granting ticket for the machine account using the host keytab.
2025-07-12 16:08:21 -05:00
Dustin f3c432dbff r/minio: Do not pull images automatically
We don't want `podman` pulling a new container image and updating
without our concent.  The image will already be there on the first
start, since we pulled it in an Ansible task.
2025-07-02 09:23:18 -05:00
Dustin 5edfbf2408 r/minio: Do not mount storage volume with :Z
The `:Z` flag tells the container runtime to run `chcon` recursively on
the specified path, in order to ensure that the files are accessible
inside the container.  For a very large volume like the MinIO storage
directory, this can take an extremely long time.  It's really only
necessary on the first startup anyway, because the context won't change
after that.  To avoid spending a bunch of time, we can set the context
correctly when we create the directory, and then not worry about it
after that.
2025-07-02 09:21:57 -05:00
Dustin 84cd6022c0 r/k8s-worker: Use K8s API to create join token
Using the Kubernetes API to create bootstrap tokens makes it possible
for the host-provisioner to automatically add new machines to the
Kubernetes cluster.  The host provisioner cannot connect to existing
machines, and thus cannot run the `kubeadm token create` command on
a control plane node.  With the appropriate permissions assigned to the
service account associated with the pod it runs in, though, it can
directly create the secret via the API.

There are actually two pieces of information required for a node to
join a cluster, though: a bootstrap token and the CA certificate.  When
using the `kubeadm token create` command to issue a bootstrap token, it
also provides (a hash of) the CA certificate with the command it prints.
When creating the token manually, we need an alternative method for
obtaining and distributing the CA certificate, so we use the
`cluster-info` ConfigMap.  This contains a stub `kubeconfig` file, which
includes the CA certificate, which can be used by the `kubeadm join`
command with a join configuration file.  Generating both of these files
may be a bit more involved than computing the CA certificate hash and
passing that on the command line, but there are a couple of advantages.
First, it's more extensible, as the join configuration file can specify
additional configuration for the node (which we may want to use later).
It's also somewhat more secure, since the token is not passed as a
command-line argument.

Interestingly, the most difficult part of this implementation was
getting the expiration timestamp.  Ansible exposes very little date math
capability; notably lacking is the ability to construct a `timedelta`
object, so the only way to get a timestamp in the future is to convert
the `datetime` object returned by `now` to a Unix timestamp and add some
number of seconds to it.  Further, there is no direct way to get a
`datetime` object from the computed Unix timestamp value, but we can
rely on the fact that Python class methods can be called on instances,
too, so `now().fromtimestamp()` works the same as
`datetime.fromtimestamp()`.
2025-07-01 08:09:11 -05:00
Dustin a5e2920223 r/victoria-logs: Update to v1.23.3 2025-06-03 18:59:25 -05:00
Dustin d4d3f0ef81 r/victoria-logs: Deploy VictoriaLogs
I've become rather frusted witih Grafana Loki lately.  It has several
bugs that affect my usage, including issues with counting and
aggregation, completely broken retention and cleanup, spamming itself
with bogus error log messages, and more.  Now that VitoriaLogs has
first-class support in Grafana and support for alerts, it seems like a
good time to try it out.  It's under very active development, with bugs
getting fixed extremely quickly, and new features added constantly.
Indeed, as I was experimenting with it, I thought, "it would be nice if
the web UI could decode ANSI escapes for terminal colors," and just a
few days later, that feature was added!  Native support for syslog is
also a huge benefit, as it will allow me to collect logs directly from
network devices, without first collecting them into a file on the Unifi
controller.

This new role deploys VictoriaLogs in a manner very similar to how I
have Loki set up, as a systemd-managed Podman container.   As it has no
built-in authentication or authorization, we rely on Caddy to handle
that.  As with Loki, mTLS is used to prevent anonymous access to
querying the logs, however, authentication via Authelia is also an
option for human+browser usage.  I'm re-using the same certificate
authority as with Loki to simplify Grafana configuration.  Eventually, I
would like to have a more robust PKI, probably using OpenBao, at which
point I will (hopefully) have decided which log database I will be
using, and can use a proper CA for it.
2025-05-30 21:19:05 -05:00
Dustin 57c5afc0c8 r/frigate: Fix Authelia redirect
HTTP 301 is "moved permanently." Browsers will cache this response and
never send the request to the real server again.  We need to use a
temporary redirect, such as "see other" to avoid getting stuck in a
login loop.
2025-04-21 08:27:34 -05:00
Dustin 113ffa2b96 r/frigate: Update to v0.15
Frigate has evolved a lot over the past year or so since v0.13.
Notably, some of the configuration options have been renamed, and
_events_ have become _alerts_ and _detections_.  There's also now
support for authenication, though we don't need it because we're using
Authelia.
2025-04-20 16:23:04 -05:00
Dustin daa59bdba5 r/useproxy: Configure dnf to use proxy
Although running `dnf` from the command line works without explicitly
configuring the proxy, because it inherits the environment variables set
by PAM on login from the user's shell, the `dnf` Ansible module does
not, as it does not inherit those variables.  Thus, we need to
explicitly configure the `proxy` setting in `dnf.conf` in order to be
able to install packages via Ansible.

Since `dnf` does not have separate settings for different protocols
(e.g. HTTP, HTTPS, FTP), we need a way to specify which of the
configured proxies to use if there are multiple.  As such, the
*useproxy* role will attempt to use the value of the `dnf_proxy`
variable, if it is set, falling back to `yum_proxy` and finally
`http_proxy`.  This should cover most situations without any explicit
configuration, but allows flexibility for other cases.
2025-03-29 09:30:08 -05:00
Dustin 923c8a3ebc r/unifi: Open firewall port for syslog server
The Unifi Network controller runs a syslog server (listening on UDP port
5514) where Unifi devices can send their logs.  We need to open the port
in the firewall in order for it to receive log messages and write them
to disk.
2025-03-29 09:27:28 -05:00
Dustin 00acc54402 Merge branch 'unifi-redux' 2025-03-29 08:03:39 -05:00
Dustin 0a956297c1 r/vmhost: Update for latest libvirt
Some time ago, _libvirt_ was refactored to use separate daemons and
sockets for each of its responsibilities, and the original "monolithic"
`libvirtd` was made obsolete.  The Fedora packages have more recently
been adjusted to favor this new approach, and now default to omitting
the monolithic daemon entirely (when `install_weak_deps` is disabled).

One interesting packaging snafu, though, is that without the weak
dependencies, there is _no_ way for clients to connect by default.
Clients run `which virt-ssh-helper` to see if it is installed, which it
is, but `which` is not.  They then fall back to running `nc`, which is
_also_ not installed.  So even though the tools they actually need are
present, their logic for detecting this is broken.  As such, we need to
explicitly install `which` to satisfy them.
2025-03-29 07:34:58 -05:00
Dustin db5d1fb91a unifi: Switch from nginx to Caddy
Mostly for built-in ACME support.
2025-03-16 17:17:00 -05:00
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 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 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 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 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 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 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 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