summaryrefslogtreecommitdiffstats
path: root/nixos/modules/services/hardware/rauc.nix
blob: e42d7d02a5ae789f774fbf99989d5960467bc932 (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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
{
  lib,
  pkgs,
  config,
  ...
}:

let
  inherit (lib.options) mkEnableOption mkPackageOption mkOption;
  inherit (lib.modules) mkMerge mkIf;
  inherit (lib.lists) flatten filter imap0;
  inherit (lib.attrsets)
    recursiveUpdate
    mapAttrsToList
    listToAttrs
    nameValuePair
    ;
  inherit (lib.strings) concatStringsSep;
  inherit (lib) types;

  cfg = config.services.rauc;
  format = pkgs.formats.ini { };

  mountDir = "${cfg.dataDir}/mnt";

  mkSlot =
    slot:
    if slot.enable then
      slot.settings
      // {
        inherit (slot) device type;
      }
    else
      null;

  slotSections = listToAttrs (
    filter (slot: slot != null) (
      flatten (
        mapAttrsToList (
          name: indexes: imap0 (idx: slot: nameValuePair "slot.${name}.${toString idx}" (mkSlot slot)) indexes
        ) cfg.slots
      )
    )
  );

  configFile = format.generate "rauc.conf" (
    recursiveUpdate cfg.settings (
      recursiveUpdate {
        system = {
          inherit (cfg) compatible bootloader;
          bundle-formats = concatStringsSep " " cfg.bundleFormats;
          data-directory = cfg.dataDir;
          mountprefix = mountDir;
        };
      } slotSections
    )
  );
in
{
  options = {
    services.rauc = {
      enable = mkEnableOption "RAUC A/B update service";
      mark-good.enable = mkEnableOption "RAUC Good-marking service";
      client.enable = mkEnableOption "RAUC client in the system environment";
      package = mkPackageOption pkgs "rauc" { };
      compatible = mkOption {
        description = "The compatibility string for this system. Can be any format so long as you are consistent.";
        type = types.str;
        example = "nix/appliance/foo";
      };
      bootloader = mkOption {
        description = "The bootloader backend for RAUC.";
        type = types.enum [
          "barebox"
          "grub"
          "uboot"
          "efi"
          "custom"
          "noop"
        ];
        example = "grub";
      };
      bundleFormats = mkOption {
        description = "Allowable formats for the RAUC bundle.";
        type = with types; listOf str;
        default = [
          "-plain"
          "+verity"
        ];
        example = [
          "-plain"
          "+verity"
        ];
      };
      dataDir = mkOption {
        description = "The state directory for RAUC.";
        default = "/var/lib/rauc";
        type = types.path;
      };
      slots = mkOption {
        description = "RAUC slot definitions. Every key is a slot class and every value is a list of slot indexes.";
        default = { };
        type = types.attrsOf (
          types.listOf (
            types.submodule {
              options = {
                enable = mkEnableOption "this RAUC slot";
                device = mkOption {
                  description = "The device to update.";
                  type = types.str;
                };
                type = mkOption {
                  description = "The type of the device.";
                  type = types.enum [
                    "raw"
                    "nand"
                    "nor"
                    "ubivol"
                    "ubifs"
                    "ext4"
                    "vfat"
                  ];
                  default = "raw";
                };
                settings = mkOption {
                  description = "Settings for this slot.";
                  type = types.attrs;
                  default = { };
                };
              };
            }
          )
        );
      };
      settings = mkOption {
        type = format.type;
        default = { };
        description = ''
          Rauc configuration that will be converted to INI. Refer to:
          <https://rauc.readthedocs.io/en/latest/reference.html#sec-ref-slot-config>
          for details on supported values.

          All module-specific options override these.
        '';
      };
    };
  };

  config = mkMerge [
    (mkIf cfg.enable {
      systemd.services.rauc = {
        description = "RAUC Update Service";
        documentation = [ "https://rauc.readthedocs.io" ];
        wants = [ "basic.target" ];
        wantedBy = [ "multi-user.target" ];
        after = [
          "dbus.service"
        ];
        serviceConfig = {
          Type = "dbus";
          BusName = "de.pengutronix.rauc";
          ExecStart = "${lib.getExe cfg.package} --conf=${configFile} --mount=/run/rauc/mnt service";
          RuntimeDirectory = "rauc/mnt";
          MountFlags = "slave";
          StateDirectory = baseNameOf cfg.dataDir;
          WorkingDirectory = cfg.dataDir;
        };
      };
      systemd.tmpfiles.rules = [
        "d ${cfg.dataDir} 0750 root root - -"
        "d ${mountDir} 0750 root root - -"
      ];
    })
    (mkIf (cfg.enable && cfg.client.enable) {
      services.dbus.packages = [ cfg.package ];
      environment.systemPackages = [ cfg.package ];
    })
    (mkIf (cfg.enable && cfg.mark-good.enable) {
      systemd.services.rauc-mark-good = {
        description = "RAUC Good-marking service";
        documentation = [ "https://rauc.readthedocs.io" ];
        wantedBy = [ "multi-user.target" ];
        after = [
          "rauc.service"
          "multi-user.target"
        ];
        serviceConfig = {
          Type = "oneshot";
          ExecStart = "${lib.getExe cfg.package} --conf=${configFile} status mark-good";
        };
      };
    })
  ];

  meta.maintainers = with lib.maintainers; [ numinit ];
}