summaryrefslogtreecommitdiffstats
path: root/nixos/modules/virtualisation/virtualbox-guest.nix
blob: 882cdf1f0cdec9af57cf5f78e9f33603b92eb2a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# Module for VirtualBox guests.
{
  config,
  lib,
  pkgs,
  ...
}:
let
  cfg = config.virtualisation.virtualbox.guest;
  kernel = config.boot.kernelPackages;

  mkVirtualBoxUserService = serviceArgs: verbose: {
    description = "VirtualBox Guest User Services ${serviceArgs}";

    wantedBy = [ "graphical-session.target" ];
    partOf = [ "graphical-session.target" ];

    # The graphical session may not be ready when starting the service
    # Hence, check if the DISPLAY env var is set, otherwise fail, wait and retry again
    startLimitBurst = 20;

    unitConfig.ConditionVirtualization = "oracle";

    # Check if the display environment is ready, otherwise fail
    preStart = "${pkgs.bash}/bin/bash -c \"if [ -z $DISPLAY ]; then exit 1; fi\"";
    serviceConfig = {
      ExecStart =
        "@${kernel.virtualboxGuestAdditions}/bin/VBoxClient"
        + (lib.strings.optionalString verbose " --verbose")
        + " --foreground ${serviceArgs}";
      # Wait after a failure, hoping that the display environment is ready after waiting
      RestartSec = 2;
      Restart = "always";
    };
  };

  mkVirtualBoxUserX11OnlyService =
    serviceArgs: verbose:
    (mkVirtualBoxUserService serviceArgs verbose)
    // {
      unitConfig.ConditionEnvironment = "XDG_SESSION_TYPE=x11";
    };
in
{
  imports = [
    (lib.mkRenamedOptionModule
      [
        "virtualisation"
        "virtualbox"
        "guest"
        "draganddrop"
      ]
      [
        "virtualisation"
        "virtualbox"
        "guest"
        "dragAndDrop"
      ]
    )
  ];

  options.virtualisation.virtualbox.guest = {
    enable = lib.mkOption {
      default = false;
      type = lib.types.bool;
      description = "Whether to enable the VirtualBox service and other guest additions.";
    };

    clipboard = lib.mkOption {
      default = true;
      type = lib.types.bool;
      description = "Whether to enable clipboard support.";
    };

    seamless = lib.mkOption {
      default = true;
      type = lib.types.bool;
      description = "Whether to enable seamless mode. When activated windows from the guest appear next to the windows of the host.";
    };

    dragAndDrop = lib.mkOption {
      default = true;
      type = lib.types.bool;
      description = "Whether to enable drag and drop support.";
    };

    verbose = lib.mkOption {
      default = false;
      type = lib.types.bool;
      description = "Whether to verbose logging for guest services.";
    };

    vboxsf = lib.mkOption {
      default = true;
      type = lib.types.bool;
      description = "Whether to load vboxsf";
    };

    use3rdPartyModules = lib.mkOption {
      default = true;
      type = lib.types.bool;
      description = "Whether to use the kernel modules provided by VirtualBox instead of the ones from the upstream kernel.";
    };
  };

  ###### implementation

  config = lib.mkIf cfg.enable (
    lib.mkMerge [
      {
        assertions = [
          {
            assertion = pkgs.stdenv.hostPlatform.isx86 || pkgs.stdenv.hostPlatform.isAarch64;
            message = "Virtualbox not currently supported on ${pkgs.stdenv.hostPlatform.system}";
          }
        ];

        environment.systemPackages = [ kernel.virtualboxGuestAdditions ];

        boot.extraModulePackages = lib.mkIf cfg.use3rdPartyModules [ kernel.virtualboxGuestAdditions ];

        systemd.services.virtualbox = {
          description = "VirtualBox Guest Services";

          wantedBy = [ "multi-user.target" ];
          requires = [ "dev-vboxguest.device" ];
          after = [ "dev-vboxguest.device" ];

          unitConfig.ConditionVirtualization = "oracle";

          serviceConfig.ExecStart = "@${kernel.virtualboxGuestAdditions}/bin/VBoxService VBoxService --foreground";
        };

        users.groups.vboxuserdev = { };

        services.udev.packages = lib.singleton (
          pkgs.writeTextFile {
            name = "virtualboxGuestAdditions";
            text = ''
              # /dev/vboxuser is necessary for VBoxClient to work
              KERNEL=="vboxuser", OWNER="root", GROUP="vboxuserdev", MODE="0660", TAG+="uaccess"

              # Allow systemd dependencies on vboxguest.
              SUBSYSTEM=="misc", KERNEL=="vboxguest", TAG+="systemd"
            '';
            destination = "/etc/udev/rules.d/70-virtualboxGuestAdditions.rules";
          }
        );

        systemd.user.services.virtualboxClientVmsvga = mkVirtualBoxUserService "--vmsvga-session" cfg.verbose;
      }
      (lib.mkIf cfg.vboxsf {
        boot.supportedFilesystems = [ "vboxsf" ];
        boot.initrd.supportedFilesystems = [ "vboxsf" ];

        users.groups.vboxsf.gid = config.ids.gids.vboxsf;
      })
      (lib.mkIf cfg.clipboard {
        systemd.user.services.virtualboxClientClipboard = mkVirtualBoxUserService "--clipboard" cfg.verbose;
      })
      (lib.mkIf cfg.seamless {
        systemd.user.services.virtualboxClientSeamless = mkVirtualBoxUserX11OnlyService "--seamless" cfg.verbose;
      })
      (lib.mkIf cfg.dragAndDrop {
        systemd.user.services.virtualboxClientDragAndDrop = mkVirtualBoxUserService "--draganddrop" cfg.verbose;
      })
    ]
  );
}