bind-dirs allows an AppVM to persist changes outside user directories.
But what if a TVM wants to persist changes inside user directories?
I'm seeking feedback on a system I set up to solve this problem: Is
there a better way to achieve this goal? Or on the contrary, is this
something that might be useful to merge in to Qubes?
Motivation
==========
These days, a lot of software is installed in the home directory and
updated outside of the package manager. We want a way to share those
packages across all AppVMs of a TVM.
E.g. say you want to install Doom Emacs, configure it, then make it
available to all AppVMs of a template. This would require the TVM to:
1. Make /home/user/.config/{doom,emacs} available in AppVMs
2. (ideally) allow AppVMs to store persistent changes to these to allow
customization on a per-VM basis
Solution
========
The solution I came up with was to have a bash script read
a file /etc/underlay/underlay.list and, for each item:
1. mount /etc/underlay/item to /home/user/item, read-only
2. mount /home/user/.overlay/item over that mount, read-write
So the files come from the TVM, but any changes are written (CoW) to the
overlay in /home/user/.overlay where they are persisted.
The procedure for updating would be to perform the update in a dispVM
based on the TVM, then copy the update to the TVM (/etc/underlay/...).
Implementation
==============
In the TVM, create:
/usr/bin/mount-underlays.sh
#!/bin/bash
# Only run in AppVMs
if [ ! -f /var/run/qubes/this-is-appvm ]; then
echo "Error: This should only be run in AppVMs" >&2
exit 8
fi
DEST="/home/user"
UNDERLAY_BASE="/etc/underlay"
OVERLAY_BASE="$DEST/.overlay"
WORKDIR_BASE="$DEST/.cache/overlay-workdir"
SYSTEM_LIST="$UNDERLAY_BASE/underlay.list"
USER_LIST="$OVERLAY_BASE/underlay.list"
# Function to mount a directory with overlay
mount_overlay() {
local rel_path="$1"
local lower_dir="$UNDERLAY_BASE/$rel_path"
local mount_point="$DEST/$rel_path"
local upper_dir="$OVERLAY_BASE/$rel_path"
local work_dir="$WORKDIR_BASE/$rel_path"
# Check if lower directory exists
if [ ! -d "$lower_dir" ]; then
echo "Warning: underlay directory $lower_dir does not exist,
skipping" >&2
return 1
fi
# Create necessary directories
mkdir -p "$mount_point" "$upper_dir" "$work_dir"
# Check if already mounted
if mountpoint -q "$mount_point" 2>/dev/null; then
echo "Already mounted: $mount_point" >&2
return 0
fi
# Mount overlay filesystem
if sudo mount -t overlay overlay \
-o lowerdir="$lower_dir",upperdir="$upper_dir",workdir="$work_dir" \
"$mount_point" 2>/dev/null; then
echo "Mounted: $rel_path at $mount_point" >&2
else
echo "Failed to mount overlay for $rel_path" >&2
return 1
fi
}
# Function to process a list file
process_list() {
local list_file="$1"
if [ ! -f "$list_file" ]; then
# list file does not exist
return 0
fi
echo "Processing: $list_file" >&2
while IFS= read -r line; do
# Skip comments and empty lines
# / at the start of a line denotes a comment
[[ "$line" == /* ]] && continue
[[ -z "$line" ]] && continue
# Mount the overlay
mount_overlay "$line"
done < "$list_file"
}
# Only proceed if the underlay base exists
if [ ! -d "$UNDERLAY_BASE" ]; then
echo "Error: Underlay base ($UNDERLAY_BASE) does not exist" >&2
exit 3
fi
process_list "$SYSTEM_LIST"
process_list "$USER_LIST"
/etc/systemd/system/mount-underlays.service
[Unit]
Description=Mount underlay directories
After=local-fs.target
ConditionPathExists=/var/run/qubes/this-is-appvm
ConditionPathExists=/etc/underlay
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=-/usr/bin/mount-underlays.sh
StandardOutput=journal
StandardError=journal
TimeoutSec=12
[Install]
WantedBy=default.target
Example Usage
=============
Example with Doom Emacs:
In the TVM:
1. In the TVM, create /etc/underlays/.config/{doom,emacs}
2. Copy your doom and emacs directories there. In the AppVM:
tar -cf emacs.tar.gz ~/.config/emacs
qvm-copy emacs.tar.gz
3. In the TVM, create /etc/underlays/underlays.list
.config/doom
.config/emacs
4. Power off the TVM.
5. Start a new disposable based on the TVM. In that, run:
systemctl start mount-underlays
emacs
--
Thanks for reading,
Zaz
--
You received this message because you are subscribed to the Google Groups
"qubes-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/qubes-devel/878qi9l7k6.fsf%40localhost.