system-manager/nix/modules/default.nix

272 lines
7.6 KiB
Nix

{ lib
, config
, pkgs
, system-manager
, ...
}:
{
imports = [
./environment.nix
./etc.nix
./systemd.nix
./tmpfiles.nix
./upstream/nixpkgs
];
options =
let
inherit (lib) types;
in
{
nixpkgs = {
# TODO: switch to lib.systems.parsedPlatform
hostPlatform = lib.mkOption {
type = types.str;
example = "x86_64-linux";
default = throw "the option nixpkgs.hostPlatform needs to be set.";
};
};
assertions = lib.mkOption {
type = types.listOf types.unspecified;
internal = true;
default = [ ];
example = [{ assertion = false; message = "you can't enable this for that reason"; }];
description = lib.mdDoc ''
This option allows modules to express conditions that must
hold for the evaluation of the system configuration to
succeed, along with associated error messages for the user.
'';
};
warnings = lib.mkOption {
internal = true;
default = [ ];
type = types.listOf types.str;
example = [ "The `foo' service is deprecated and will go away soon!" ];
description = lib.mdDoc ''
This option allows modules to show warnings to users during
the evaluation of the system configuration.
'';
};
# Statically assigned UIDs and GIDs.
# Ideally we use DynamicUser as much as possible to avoid the need for these.
ids = {
uids = lib.mkOption {
internal = true;
description = lib.mdDoc ''
The user IDs used by system-manager.
'';
type = types.attrsOf types.int;
};
gids = lib.mkOption {
internal = true;
description = lib.mdDoc ''
The group IDs used by system-manager.
'';
type = types.attrsOf types.int;
};
};
# No-op option for now.
# TODO: should we include the settings in /etc/logrotate.d ?
services.logrotate = lib.mkOption {
internal = true;
default = { };
type = types.attrs;
};
# No-op option for now.
users = lib.mkOption {
internal = true;
default = { };
type = types.attrs;
};
networking = {
enableIPv6 = lib.mkEnableOption "IPv6" // {
default = true;
};
};
system-manager = {
allowAnyDistro = lib.mkEnableOption "the usage of system-manager on untested distributions";
preActivationAssertions = lib.mkOption {
type = with lib.types; attrsOf (submodule ({ name, ... }: {
options = {
enable = lib.mkEnableOption "the assertion";
name = lib.mkOption {
type = types.str;
default = name;
};
script = lib.mkOption {
type = types.str;
};
};
}));
default = { };
};
};
build = {
scripts = lib.mkOption {
type = lib.types.attrsOf lib.types.package;
};
etc = {
staticEnv = lib.mkOption {
type = lib.types.package;
};
entries = lib.mkOption {
# TODO: better type
type = lib.types.attrsOf lib.types.raw;
};
};
services = lib.mkOption {
# TODO: better type
type = lib.types.attrsOf lib.types.raw;
};
};
};
config = {
system-manager.preActivationAssertions = {
osVersion =
let
supportedIds = [ "nixos" "ubuntu" ];
in
{
enable = !config.system-manager.allowAnyDistro;
script = ''
source /etc/os-release
${lib.concatStringsSep "\n" (lib.flip map supportedIds (supportedId: ''
if [ $ID = "${supportedId}" ]; then
exit 0
fi
''))}
echo "This OS is not currently supported."
echo "Supported OSs are: ${lib.concatStringsSep ", " supportedIds}"
exit 1
'';
};
};
build = {
scripts = {
registerProfileScript = pkgs.writeShellScript "register-profile" ''
${system-manager}/bin/system-manager register \
--store-path "$(dirname $(realpath $(dirname ''${0})))" \
"$@"
'';
activationScript = pkgs.writeShellScript "activate" ''
${system-manager}/bin/system-manager activate \
--store-path "$(dirname $(realpath $(dirname ''${0})))" \
"$@"
'';
prepopulateScript = pkgs.writeShellScript "prepopulate" ''
${system-manager}/bin/system-manager pre-populate \
--store-path "$(dirname $(realpath $(dirname ''${0})))" \
"$@"
'';
deactivationScript = pkgs.writeShellScript "deactivate" ''
${system-manager}/bin/system-manager deactivate "$@"
'';
preActivationAssertionScript =
let
mkAssertion = { name, script, ... }: ''
# ${name}
echo -e "Evaluating pre-activation assertion ${name}...\n"
(
set +e
${script}
)
assertion_result=$?
if [ $assertion_result -ne 0 ]; then
failed_assertions+=${name}
fi
'';
mkAssertions = assertions:
lib.concatStringsSep "\n" (
lib.mapAttrsToList (name: mkAssertion) (
lib.filterAttrs (name: cfg: cfg.enable)
assertions
)
);
in
pkgs.writeShellScript "preActivationAssertions" ''
set -ou pipefail
declare -a failed_assertions=()
${mkAssertions config.system-manager.preActivationAssertions}
if [ ''${#failed_assertions[@]} -ne 0 ]; then
for failed_assertion in ''${failed_assertions[@]}; do
echo "Pre-activation assertion $failed_assertion failed."
done
echo "See the output above for more details."
exit 1
else
echo "All pre-activation assertions succeeded."
exit 0
fi
'';
};
# TODO: handle globbing
etc =
let
addToStore = name: file: pkgs.runCommandLocal "${name}-etc-link" { } ''
mkdir -p "$out/$(dirname "${file.target}")"
ln -s "${file.source}" "$out/${file.target}"
if [ "${file.mode}" != symlink ]; then
echo "${file.mode}" > "$out/${file.target}.mode"
echo "${file.user}" > "$out/${file.target}.uid"
echo "${file.group}" > "$out/${file.target}.gid"
fi
'';
filteredEntries = lib.filterAttrs
(_name: etcFile: etcFile.enable)
config.environment.etc;
srcDrvs = lib.mapAttrs addToStore filteredEntries;
entries = lib.mapAttrs
(name: file: file // { source = "${srcDrvs.${name}}"; })
filteredEntries;
staticEnv = pkgs.buildEnv {
name = "etc-static-env";
paths = lib.attrValues srcDrvs;
};
in
{ inherit entries staticEnv; };
services =
lib.mapAttrs'
(unitName: unit:
lib.nameValuePair unitName {
storePath =
''${unit.unit}/${unitName}'';
})
(lib.filterAttrs (_: unit: unit.enable)
config.systemd.units);
};
};
}