|
| 1 | +{ |
| 2 | + config, |
| 3 | + options, |
| 4 | + lib, |
| 5 | + diskoLib, |
| 6 | + rootMountPoint, |
| 7 | + ... |
| 8 | +}: |
| 9 | +{ |
| 10 | + options = { |
| 11 | + name = lib.mkOption { |
| 12 | + type = lib.types.str; |
| 13 | + default = config._module.args.name; |
| 14 | + description = "Name of the bcache set."; |
| 15 | + example = "main"; |
| 16 | + }; |
| 17 | + type = lib.mkOption { |
| 18 | + type = lib.types.enum [ "bcache" ]; |
| 19 | + internal = true; |
| 20 | + description = "Type"; |
| 21 | + }; |
| 22 | + device = lib.mkOption { |
| 23 | + type = lib.types.str; |
| 24 | + default = "/dev/bcache0"; |
| 25 | + description = '' |
| 26 | + The bcache block device path. |
| 27 | + For single-backing setups this is always `/dev/bcache0`. |
| 28 | + For multi-backing setups, set this explicitly. |
| 29 | + ''; |
| 30 | + }; |
| 31 | + cacheMode = lib.mkOption { |
| 32 | + type = lib.types.enum [ |
| 33 | + "writethrough" |
| 34 | + "writeback" |
| 35 | + "writearound" |
| 36 | + "none" |
| 37 | + ]; |
| 38 | + default = "writethrough"; |
| 39 | + description = "Cache mode for the bcache device."; |
| 40 | + }; |
| 41 | + extraCacheArgs = lib.mkOption { |
| 42 | + type = lib.types.listOf lib.types.str; |
| 43 | + default = [ ]; |
| 44 | + description = "Extra arguments passed to `make-bcache -C` for the cache device."; |
| 45 | + }; |
| 46 | + extraBackingArgs = lib.mkOption { |
| 47 | + type = lib.types.listOf lib.types.str; |
| 48 | + default = [ ]; |
| 49 | + description = "Extra arguments passed to `make-bcache -B` for the backing device."; |
| 50 | + }; |
| 51 | + content = diskoLib.deviceType { |
| 52 | + parent = config; |
| 53 | + device = config.device; |
| 54 | + }; |
| 55 | + _meta = lib.mkOption { |
| 56 | + internal = true; |
| 57 | + readOnly = true; |
| 58 | + type = diskoLib.jsonType; |
| 59 | + default = lib.optionalAttrs (config.content != null) ( |
| 60 | + config.content._meta [ |
| 61 | + "bcache" |
| 62 | + config.name |
| 63 | + ] |
| 64 | + ); |
| 65 | + description = "Metadata"; |
| 66 | + }; |
| 67 | + _create = diskoLib.mkCreateOption { |
| 68 | + inherit config options; |
| 69 | + default = |
| 70 | + let |
| 71 | + devBasename = builtins.baseNameOf config.device; |
| 72 | + in |
| 73 | + '' |
| 74 | + # Read cache and backing device paths from temp directory |
| 75 | + if ! test -s "$disko_devices_dir/bcache_cache_${lib.escapeShellArg config.name}"; then |
| 76 | + printf "\033[31mERROR:\033[0m No cache device found for bcache set \"${config.name}\"!\n" >&2 |
| 77 | + exit 1 |
| 78 | + fi |
| 79 | + if ! test -s "$disko_devices_dir/bcache_backing_${lib.escapeShellArg config.name}"; then |
| 80 | + printf "\033[31mERROR:\033[0m No backing device found for bcache set \"${config.name}\"!\n" >&2 |
| 81 | + exit 1 |
| 82 | + fi |
| 83 | +
|
| 84 | + cache_dev=$(head -n1 "$disko_devices_dir/bcache_cache_${lib.escapeShellArg config.name}") |
| 85 | + backing_dev=$(head -n1 "$disko_devices_dir/bcache_backing_${lib.escapeShellArg config.name}") |
| 86 | +
|
| 87 | + modprobe bcache |
| 88 | +
|
| 89 | + # Resolve symlink to kernel device name for sysfs matching |
| 90 | + backing_basename="$(basename "$(readlink -f "$backing_dev")")" |
| 91 | +
|
| 92 | + # Idempotency: if the bcache device already exists with our backing device, skip creation |
| 93 | + if [ -b "${config.device}" ] && \ |
| 94 | + [ -e "/sys/block/${devBasename}/bcache/backing_dev_name" ] && \ |
| 95 | + [ "$(cat "/sys/block/${devBasename}/bcache/backing_dev_name")" = "$backing_basename" ]; then |
| 96 | + : # bcache device already active with correct backing — nothing to do |
| 97 | + else |
| 98 | + # Teardown stale bcache device if it exists (destroy-format-mount scenario) |
| 99 | + if [ -e "/sys/block/${devBasename}/bcache/stop" ]; then |
| 100 | + echo 1 > "/sys/block/${devBasename}/bcache/stop" 2>/dev/null || true |
| 101 | + fi |
| 102 | + # Unregister any active cache sets |
| 103 | + find /sys/fs/bcache -maxdepth 1 -mindepth 1 -type d -exec sh -c ' |
| 104 | + for cset; do |
| 105 | + if [ -e "$cset/unregister" ]; then |
| 106 | + echo 1 > "$cset/unregister" 2>/dev/null || true |
| 107 | + fi |
| 108 | + done |
| 109 | + ' _ {} + 2>/dev/null || true |
| 110 | + udevadm settle --timeout=10 |
| 111 | + # Wait for the bcache device to fully disappear |
| 112 | + for i in $(seq 1 30); do |
| 113 | + [ -b "${config.device}" ] || break |
| 114 | + sleep 1 |
| 115 | + done |
| 116 | +
|
| 117 | + # Create bcache set — --force handles pre-existing superblocks |
| 118 | + make-bcache \ |
| 119 | + -C "$cache_dev" \ |
| 120 | + -B "$backing_dev" \ |
| 121 | + --force \ |
| 122 | + ${lib.optionalString (config.cacheMode == "writeback") "--writeback"} \ |
| 123 | + ${lib.concatStringsSep " " (map lib.escapeShellArg config.extraCacheArgs)} \ |
| 124 | + ${lib.concatStringsSep " " (map lib.escapeShellArg config.extraBackingArgs)} |
| 125 | +
|
| 126 | + # Wait for the bcache block device to appear |
| 127 | + for i in $(seq 1 60); do |
| 128 | + [ -b "${config.device}" ] && break |
| 129 | + sleep 1 |
| 130 | + done |
| 131 | + fi |
| 132 | +
|
| 133 | + if [ ! -b "${config.device}" ]; then |
| 134 | + printf "\033[31mERROR:\033[0m bcache device ${config.device} did not appear\n" >&2 |
| 135 | + exit 1 |
| 136 | + fi |
| 137 | +
|
| 138 | + # Set cache mode |
| 139 | + echo "${config.cacheMode}" > "/sys/block/${devBasename}/bcache/cache_mode" |
| 140 | +
|
| 141 | + ${lib.optionalString (config.content != null) config.content._create} |
| 142 | + ''; |
| 143 | + }; |
| 144 | + _mount = diskoLib.mkMountOption { |
| 145 | + inherit config options; |
| 146 | + default = lib.optionalAttrs (config.content != null) config.content._mount; |
| 147 | + }; |
| 148 | + _unmount = diskoLib.mkUnmountOption { |
| 149 | + inherit config options; |
| 150 | + default = |
| 151 | + let |
| 152 | + devBasename = builtins.baseNameOf config.device; |
| 153 | + content = lib.optionalAttrs (config.content != null) config.content._unmount; |
| 154 | + in |
| 155 | + { |
| 156 | + fs = content.fs or { }; |
| 157 | + dev = '' |
| 158 | + ${content.dev or ""} |
| 159 | + # Stop the bcache device to release cache and backing devices |
| 160 | + if [ -e "/sys/block/${devBasename}/bcache/detach" ]; then |
| 161 | + echo 1 > "/sys/block/${devBasename}/bcache/detach" 2>/dev/null || true |
| 162 | + fi |
| 163 | + if [ -e "/sys/block/${devBasename}/bcache/stop" ]; then |
| 164 | + echo 1 > "/sys/block/${devBasename}/bcache/stop" 2>/dev/null || true |
| 165 | + fi |
| 166 | + # Unregister cache sets |
| 167 | + find /sys/fs/bcache -maxdepth 1 -mindepth 1 -type d -exec sh -c ' |
| 168 | + for cset; do |
| 169 | + if [ -e "$cset/unregister" ]; then |
| 170 | + echo 1 > "$cset/unregister" 2>/dev/null || true |
| 171 | + fi |
| 172 | + done |
| 173 | + ' _ {} + 2>/dev/null || true |
| 174 | + udevadm settle --timeout=10 |
| 175 | + ''; |
| 176 | + }; |
| 177 | + }; |
| 178 | + _config = lib.mkOption { |
| 179 | + internal = true; |
| 180 | + readOnly = true; |
| 181 | + default = [ |
| 182 | + { |
| 183 | + boot.bcache.enable = true; |
| 184 | + } |
| 185 | + ] |
| 186 | + ++ lib.optional (config.content != null) config.content._config; |
| 187 | + description = "NixOS configuration"; |
| 188 | + }; |
| 189 | + _pkgs = lib.mkOption { |
| 190 | + internal = true; |
| 191 | + readOnly = true; |
| 192 | + type = lib.types.functionTo (lib.types.listOf lib.types.package); |
| 193 | + default = |
| 194 | + pkgs: |
| 195 | + [ |
| 196 | + pkgs.bcache-tools |
| 197 | + pkgs.kmod |
| 198 | + pkgs.util-linux |
| 199 | + ] |
| 200 | + ++ lib.optionals (config.content != null) (config.content._pkgs pkgs); |
| 201 | + description = "Packages"; |
| 202 | + }; |
| 203 | + }; |
| 204 | +} |
0 commit comments