Skip to content

Commit

Permalink
Host-to-guest storage module
Browse files Browse the repository at this point in the history
- System-wide service to configure shares for guest vms using virtiofs
- Persistent fingerprint storage configured for gui-vm
- Preparation for other services using this feature

Signed-off-by: Manuel Bluhm <manuel@ssrc.tii.ae>
  • Loading branch information
mbssrc committed Jun 11, 2024
1 parent 1f1cde9 commit 1057610
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 0 deletions.
1 change: 1 addition & 0 deletions modules/common/services/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
imports = [
./dendrite-pinecone.nix
./fprint.nix
./storage.nix
];
}
13 changes: 13 additions & 0 deletions modules/common/services/fprint.nix
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,18 @@ in {
};
};
};
# Enable host store for fingerprints
ghaf.services.storage.shares = [
{
tag = "fprint-store";
host-path = "/var/lib/fprint";
vm-path = "/var/lib/fprint";
target-vm = "gui-vm";
target-owner = "root";
target-group = "root";
target-permissions = "600";
target-service = "fprintd.service";
}
];
};
}
118 changes: 118 additions & 0 deletions modules/common/services/storage.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
# SPDX-License-Identifier: Apache-2.0
{
config,
lib,
...
}: let
inherit (lib) mkEnableOption mkOption types literalExpression;

storageSubmodule = types.submodule {
options = {
tag = mkOption {
type = types.nullOr types.str;
description = ''
Storage tag used as label for the microvm share.
'';
};
host-path = mkOption {
type = types.str;
description = ''
Storage directory in the host. If it does not exist, it will be generated
with the default owner 'microvm' and group 'kvm'.
'';
};
tmp-path = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
(Optional) Temporary directory in the VM to mount the host directory. If this is set,
the folder from host-path will be recursively copied from 'tmp-path' to 'vm-path' and owner, group,
and permissions applied.
If this parameter is null, the host-path is mounted to vm-path directly.
'';
};
vm-path = mkOption {
type = types.str;
description = ''
Storage directory in the guest where the share is mounted to.
'';
};
target-vm = mkOption {
type = types.str;
description = ''
Name of the VM the share is mounted into. The name must match the microvm
name in 'config.microvm.vms': e.g., "gui-vm" (note the '-').
'';
};
target-owner = mkOption {
type = types.nullOr types.str;
description = ''
Ownership to be applied after copying from 'tmp-path' or passing the files to the vm-path.
Note that the owner, group, and permissions are applied to the actual host files. This is
reset on next boot by the host storage service to allow microvm to access the files.
'';
};
target-group = mkOption {
type = types.nullOr types.str;
description = ''
Group to be applied after copying from 'tmp-path' or passing the files to the vm-path.
Note that the owner, group, and permissions are applied to the actual host files. This is
reset on next boot by the host storage service to allow microvm to access the files.
'';
};
target-permissions = mkOption {
type = types.nullOr types.str;
description = ''
Permissions to be applied after copying from 'tmp-path' or passing the files to the vm-path.
Format is an integer string (e.g., "755") as input for chmod.
Note that the owner, group, and permissions are applied to the actual host files. This is
reset on next boot by the host storage service to allow microvm to access the files.
'';
};
target-service = mkOption {
type = types.str;
default = "default.target";
description = ''
Systemd unit that requires the share. If set, the share is processed in the VM before the service is started.
Defaults to 'default.target'.
'';
};
};
};
in {
options.ghaf.services.storage = {
enable = mkEnableOption "Enable host-to-guest storage module";
shares = mkOption {
type = types.listOf storageSubmodule;
default = [];
example = literalExpression ''
[
{
tag = "fprint-store";
host-path = "/var/lib/fprint";
vm-path = "/var/lib/fprint";
target-vm = "gui-vm";
target-owner = "root";
target-group = "root";
target-permissions = "600";
target-service = "fprintd.service";
}
{
tag = "test-store";
host-path = "/var/testfolder";
tmp-path = "/var/testfolder";
vm-path = "/run/testfolder";
target-vm = "admin-vm";
target-owner = "ghaf";
target-group = "ghaf";
target-permissions = "775";
}
];
'';
description = ''
List of share configurations of type 'storageSubmodule'.
'';
};
};
}
4 changes: 4 additions & 0 deletions modules/host/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
# Modules that should be only imported to host
#
{lib, ...}: {
imports = [
./storage.nix
];

networking.hostName = lib.mkDefault "ghaf-host";

# Overlays should be only defined for host, because microvm.nix uses the
Expand Down
115 changes: 115 additions & 0 deletions modules/host/storage.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
# SPDX-License-Identifier: Apache-2.0
{
config,
lib,
pkgs,
...
}: let
cfg = config.ghaf.services.storage;
inherit (builtins) filter map listToAttrs;
inherit (lib) mkIf optionals optionalAttrs optionalString forEach nameValuePair recursiveUpdate concatStringsSep unique foldl';

# Helper functions
filterHostShares = filter (s: s.tag != null && s.host-path != null) cfg.shares;
vmShares = filter (s: s.target-vm != null) cfg.shares;
filterSharesByName = vm: filter (s: s.target-vm == vm) vmShares;
sharePaths = map (s: s.host-path) filterHostShares;
vmList = unique (map (s: s.target-vm) vmShares);
mergeAttrSets = attrSets: foldl' (a: b: recursiveUpdate a b) {} attrSets;

# Share configuration
shareConfigs = vm: {
microvm.shares = forEach (filterSharesByName vm) (
vmShare: {
tag = "${vmShare.tag}";
source = "${vmShare.host-path}";
mountPoint = "${
if (vmShare.tmp-path != null)
then vmShare.tmp-path
else vmShare.vm-path
}";
proto = "virtiofs";
}
);
};

# Service configuration
serviceConfigs = vm:
forEach (filterSharesByName vm) (vmShare: {
environment.systemPackages = optionals (vmShare.tmp-path != null) [pkgs.umount];
systemd.services."storage-prep-${vmShare.tag}" = let
# Service to set owner, permissions, and (optionally) copy files
prepStorage = pkgs.writeShellScriptBin "storage_prep" ''
set -xeuo pipefail
if [ ! -d "${vmShare.vm-path}" ]; then
mkdir -p ${vmShare.vm-path}
fi
${optionalString (vmShare.tmp-path != null) "cp -r ${vmShare.tmp-path}/* ${vmShare.vm-path}"}
chown -R ${vmShare.target-owner}:${vmShare.target-group} ${vmShare.vm-path}
chmod -R ${vmShare.target-permissions} ${vmShare.vm-path}
${optionalString (vmShare.tmp-path != null) "${pkgs.umount}/bin/umount ${vmShare.tmp-path}"}
'';
in
{
description = "Prepare host-shared files for ${vmShare.tag}";
enable = true;
path = [prepStorage];
after = ["local-fs.target"];
requiredBy = ["${vmShare.target-service}"];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
Restart = "no";
StandardOutput = "journal";
StandardError = "journal";
ExecStart = "${prepStorage}/bin/storage_prep";
};
}
// optionalAttrs (vmShare.tmp-path != null) {
unitConfig.ConditionPathExists = "${vmShare.tmp-path}";
};
});

# Assemble VM config
vmConfig = vm: {
config = mergeAttrSets [
(shareConfigs vm)
(mergeAttrSets (serviceConfigs vm))
];
};
in {
config = mkIf cfg.enable {
# Host directory generation
systemd.services."storage-prep-host" = let
prepStorage = pkgs.writeShellScriptBin "process_dirs" ''
set -xeuo pipefail
IFS=', ' read -r -a array <<< "${concatStringsSep " " sharePaths}"
for path in "''${array[@]}"; do
if [ ! -d "$path" ]; then
mkdir -p $path
fi
chmod -R 770 $path
done
'';
in {
description = "Generate host storage directories";
enable = true;
path = [prepStorage];
wantedBy = ["local-fs.target"];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
Restart = "no";
StandardOutput = "journal";
StandardError = "journal";
ExecStart = "${prepStorage}/bin/process_dirs";
};
};

# Extra VM config to be passed to the respective VMs
microvm.vms = listToAttrs (
map (vm: nameValuePair "${vm}" (vmConfig vm)) vmList
);
};
}
1 change: 1 addition & 0 deletions targets/lenovo-x1/everything.nix
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
# Service options
services = {
fprint.enable = true;
storage.enable = true;
dendrite-pinecone.enable = true;
};

Expand Down

0 comments on commit 1057610

Please sign in to comment.