Projects can now set a `buildvm` variable in their `config` file to
specify a unique name for their build VMs. This allows multiple
projects to have running VMs simultaneously.
If a project does not provide a name, the build VM will be called
`aimeebuild`.
The default SELinux policy does not allow passing generic USB devices
and unallocated TTYs to containers. This prevents the Zigbee and ZWave
USB dongles from being usable by their respective container processes.
`systemd-network-generator`, which parses network configuration from the
kernel command line and generates configuration for *systemd-networkd*,
creates a configuration file with priority 90. This conflicts with our
default network configuration at the same priority, so the configuration
from the kernel command line never gets used. By reducing our default
priority to 95, we ensure that the generated configuration file is read
first and thus overrides the defaults.
The `semanage.sh` script starts an interactive shell in an alternate
mount namespace where `/etc/selinux` and `/var/lib/selinux` are
bind-mounted from the target filesystem. This allows SELinux management
commands like `setsebool`, `semodule, `semanage`, etc. to work on the
target policy, since not all of them directly support working on an
alternate policy.
Very large packages cannot be built with `PORTAGE_TMPDIR` on a tmpfs
mount, unless the VM has a tremendous amount of RAM. The performance
improvement is negligible anyway, so let's not bother.
It turns out that QEMU microvms have a very limited number of I/O ports
available for e.g. attached disks and shared filesystems. To conserve
ports, we'll use a single volume for both distfiles and binpkgs cache
directories.
The `ocivm.sh` script mounts the source directory at `/tmp/src`, since
we use `/tmp/build` as the output directory. For consistency, the
`start-container.sh` script, although it's mostly unused anymore, now
does the same.
`genimage` does not align partitions appropriately by default, but
simply packs them one-after-another based on their size. This is easy
enough to fix by setting the `align` property in the `hdimage` block.
These SELinux policy rules allow containers to log directly to
syslog/the systemd journal via the `/dev/log` socket. To enable this,
simply mount the socket into a container's mount namespace, e.g. `-v
/dev/log:/dev/log`.
When creating writable paths in the `/etc` subvolume, the `setup_etc`
function needs to create intermediate directories before copying
existing files from the root filesystem. Without this step, `cp` will
fail with a "no such file or directory" error, referring to the
destination path.
We're going to use *nginx* as the reverse proxy in front of Home
Assistant, as well as the web consoles for Zigbee2MQTT and ZWaveJS2MQTT.
It will provide TLS termination for all of these applications.
Since *nginx* will not start without a certificate and private key file
for HTTPS, the *gen-nginx-cert.service* systemd unit generates a
self-signed certificate if one does not already exist. This ensures
that *nginx* can start by default, but still allows the administrator to
replace the certificate with a trusted one later.
The *nginx* container image has symlinks at `/var/log/nginx/error.log`
and `/var/log/nginx/access.log`, pointing to `/dev/stderr` and
`/dev/stdout`, respectively. The intent here is to send all log
messages to the container runtime. Unfortunately, when the the
container is managed by Podman from a systemd unit, the standard output
and standard error streams are connected to the systemd journal via a
UNIX socket. As a result, the `/dev/stdout` and `/dev/stderr`
pseudo-files cannot be "opened" like normal files or pipes. Thus, to
forward nginx's logs to the systemd journal correctly, we have to do a
bit of trickery. For the error log at least, setting `error_log stderr`
works well; nginx simply writes messages to the existing file
descriptor. Unfortunately, the access log has no such mechanism. For
that, we use nginx's syslog capabilities. The `/dev/log` socket is
bind-mounted into the container, and nginx is configured to connect to
it.
Aimee OS variants can now supply a `semange.mods` file in their
configuration directories. This file contains SELinux customizations,
in the format produced by `semanage export`. Variants can use this
mechanism to set SELinux booleans, change user/login mappings, and other
SELinux policy customization. At build time, the base `selinux.mods`
file in the source directory is combined with the variant's file, if it
exists, and the resultant customizations are applied in a single
transaction.
Files and directories that have restrictive permissions and/or are now
owned by *root:root* require `cp` to have additional process
capabilities in order to copy them to the writable filesystem.
The `mksquashfs` command accepts a `-pf` argument, which specifies a
file that defines "pseudo files." Each line in the file defines some
file path and an operation to perform on that path *in the generated
filesystem image*. This allows, e.g. creating files, changing their
permissions, etc. that cannot be done in the source directory for
whatever reason.
Aimee OS now allows a variant to provide a pseudo file definition file
for `mksquashfs` by creating a `squashfs.pseudo` file in its
configuration directory.
We'll use BURP for backups, just like the old Home Assistant server.
Note that Portage cannot correctly set the ownership of files when
installing to an alternate $ROOT. To work around this, we replace the
`fowners` function for the *app-backup/burp* ebuild with a no-op, and
then set the permissions of the relevant files using SquashFS
pseudo-file definitions. Relatedly, we're omitting the files and
directories used by the server-side of BURP.
The `systemd_tmpfiles_manage_all` SELinux boolean allows
systemd-tmpfiles to manage any file, not just the (very small) subset
allowed by the default SELinux policy. Since we're using
systemd-tmpfiles to create directories and subvolumes for our
applications, we need this setting enabled.
The default Home Assistant configuration does not allow running a
reverse proxy in front of the application. To enable this, the
`use_x_forwarded_for` and `trusted_proxies` options have to be set.
Since we want `/var/lib/homeassistant` to be a Btrfs subvolume, we can't
simply include the necessary files in the correct location in the rootfs
image. Instead, we must define "copy tree" (`C`) actions for
`systemd-tmpfiles` to copy them from `/usr/share/factory`.
Unfortunately, `systemd-tmpfiles` considers `v` and `C` actions
conflicting, and thus will not copy the directory contents recursively.
Each file has to be listed explicitly.
The default SELinux policy for Podman only allows it to relabel certain
files, such as `user_home_t`, to `container_file_t`. This effectively
precludes mounting arbitrary directories from `/var` into containers.
When troubleshooting issues with the *init-storage* service, it is
helpful to be able to see the output from it directly on the system
(serial) console. If the initialization fails in certain situations, it
may be impossible to see the output otherwise. For this to work,
though, the unit has to be activated after the systemd journal has
started.
If the data volume is a partition (rather than a whole disk device), the
`init-storage` script will now attempt to resize it to use all
unallocated space on the disk. This only works if it is the last
partition on the disk. This situation is common when writing the
`sdcard.img` file directly to a disk that is larger than 4 GiB.
Apparently `FEATURES` and/or `EMERGE_DEFAULT_OPTS` set in `make.conf` do
not seem to apply to packages pulled in as `BDEPEND` dependencies and
installed in `/` instead of `/usr/${target}`. This causes binary
packages to not be built/used for those packages. Passing `--buildpkg`
(`-b`) and `--usepkg` (`-k`) on the command line ensures that binary
packages are built for everything.
Variants no longer need to specify the full contents of the
`squashfs.exclude` file. The "base" file in the source directory gets
combined with the one from the config directory (if it exists).
There's really no sense in having binary packages for
*sys-boot/raspberrypi-firmware*, since there is no "source" to build.
Having binary packages just wastes space.
By default, `systemd-tmpfiles` will create normal directories instead of
Btrfs subvolumes unless `/` is already a subvolume. According to
[Lennart][0], this has to do with subvolumes being too "heavy-weight,"
whatever that means.
Fortunately, we can override this nonsense with an environment variable.
[0]: https://github.com/systemd/systemd/pull/1915
Somewhat expectedly, attempting to avoid installing *app-admin/setools*
by listing it in `/etc/portage/profile/package.provided` proved more
trouble than it's worth.
Custom builds of Aimee OS can now specify additional paths under `/etc`
that should be writable. This is accomplished by populating a file
named `/etc/aimee-os/writable-etc` with a list of paths. Each line must
indicate the type of file (regular file: `f`, directory: `d`) and the
*relative* path under `/etc`.
It seems the bug that caused udev rules to be installed in the wrong
location has been fixed. As such, we need to make this corrective
action step conditional, only moving rules files if any are found in the
wrong place.
If multiple patches are provided for the same package, we need to ensure
that they all applied. Previously, only the last patch was applied,
because the ebuilds were copied from the main repository each time,
undoing all previous patches.
The base Aimee OS build does not need any post-installation tasks.
Custom builds can provide a `post-build.sh` script to implement the
tasks they need. For example, builds targeting Raspberry Pi devices
can use this script to install the firmware files.
The `build.packages` and `install.packages` files in the CONFIGDIR now
only need to include *additional* packages to install. The packages
*required* for Aimee OS are always installed, listed in the
corresponding files in the source directory.
Since the container images we're using as a base for the build system
only contain stable packages, setting ACCEPT_KEYWORDS to allow unstable
packages globally can cause a lot of rebuilds and potentially break
things. Instead, we only set ~arch for the packages we actually need
recent versions on the host.
This does not affect packages installed in the target root, of course.
As the scope of Aimee OS grows, and other applications are added to it,
the `init-storage` command will have an ever-growing list of file and
directory types to copy from the rootfs image. Originally, I wanted to
explicitly allow it to only copy files that are found in `/var`, but
this will become untenable very quickly. As such, to avoid having to
constantly update the SELinux policy for every new application that
stores anything in `/var` at install time, the `aimee_storinit_t` domain
can now manage all "non-security" files, directories, and symbolic
links. This covers pretty much everything in `/var` except
`/var/log/audit`, while still excluding the most sensitive files (e.g.
`/etc/shadow`),