{ config, lib, pkgs, ... }: let cfg = config.services.coronafs; tomlFormat = pkgs.formats.toml { }; coronafsConfigFile = tomlFormat.generate "coronafs.toml" { listen_addr = "0.0.0.0:${toString cfg.port}"; advertise_host = cfg.advertiseHost; data_dir = toString cfg.dataDir; export_bind_addr = cfg.exportBindAddr; export_base_port = cfg.exportBasePort; export_port_count = cfg.exportPortCount; export_shared_clients = cfg.exportSharedClients; export_cache_mode = cfg.exportCacheMode; export_aio_mode = cfg.exportAioMode; export_discard_mode = cfg.exportDiscardMode; export_detect_zeroes_mode = cfg.exportDetectZeroesMode; preallocate = cfg.preallocate; sync_on_write = cfg.syncOnWrite; qemu_nbd_path = "${pkgs.qemu}/bin/qemu-nbd"; qemu_img_path = "${pkgs.qemu}/bin/qemu-img"; log_level = "info"; }; in { options.services.coronafs = { enable = lib.mkEnableOption "CoronaFS block volume service"; port = lib.mkOption { type = lib.types.port; default = 50088; description = "Port for the CoronaFS control API."; }; advertiseHost = lib.mkOption { type = lib.types.str; default = "127.0.0.1"; description = "Host or IP placed into exported NBD URIs."; example = "10.0.0.11"; }; exportBindAddr = lib.mkOption { type = lib.types.str; default = "0.0.0.0"; description = "Bind address for qemu-nbd exports."; }; exportBasePort = lib.mkOption { type = lib.types.port; default = 11000; description = "First TCP port reserved for CoronaFS NBD exports."; }; exportPortCount = lib.mkOption { type = lib.types.int; default = 512; description = "Number of NBD export ports reserved for CoronaFS volumes."; }; exportSharedClients = lib.mkOption { type = lib.types.int; default = 32; description = "Maximum number of concurrent clients per exported CoronaFS volume."; }; exportCacheMode = lib.mkOption { type = lib.types.enum [ "none" "writeback" "writethrough" "directsync" "unsafe" ]; default = "none"; description = "qemu-nbd cache mode for CoronaFS exports."; }; exportAioMode = lib.mkOption { type = lib.types.enum [ "native" "io_uring" "threads" ]; default = "io_uring"; description = "qemu-nbd AIO mode for CoronaFS exports."; }; exportDiscardMode = lib.mkOption { type = lib.types.enum [ "ignore" "unmap" ]; default = "unmap"; description = "qemu-nbd discard handling for CoronaFS exports."; }; exportDetectZeroesMode = lib.mkOption { type = lib.types.enum [ "off" "on" "unmap" ]; default = "unmap"; description = "qemu-nbd detect-zeroes mode for CoronaFS exports."; }; preallocate = lib.mkOption { type = lib.types.bool; default = true; description = "Preallocate blank CoronaFS volumes with fallocate when possible."; }; syncOnWrite = lib.mkOption { type = lib.types.bool; default = false; description = "Force sync_all after volume import writes."; }; dataDir = lib.mkOption { type = lib.types.path; default = "/var/lib/coronafs"; description = "Data directory for CoronaFS volumes, metadata, and export pid files."; }; package = lib.mkOption { type = lib.types.package; default = pkgs.coronafs-server or (throw "coronafs-server package not found"); description = "Package to use for CoronaFS."; }; }; config = lib.mkIf cfg.enable { users.users.coronafs = { isSystemUser = true; group = "coronafs"; description = "CoronaFS service user"; home = cfg.dataDir; }; users.groups.coronafs = { }; systemd.services.coronafs = { description = "CoronaFS Block Volume Service"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; path = [ pkgs.qemu pkgs.util-linux pkgs.procps pkgs.coreutils ]; serviceConfig = { Type = "simple"; User = "coronafs"; Group = "coronafs"; UMask = "0007"; Restart = "on-failure"; RestartSec = "5s"; StateDirectory = "coronafs"; StateDirectoryMode = "0750"; ReadWritePaths = [ cfg.dataDir ]; ExecStart = "${cfg.package}/bin/coronafs-server --config ${coronafsConfigFile}"; }; }; systemd.tmpfiles.rules = [ "d ${toString cfg.dataDir} 0750 coronafs coronafs -" "d ${toString cfg.dataDir}/volumes 0750 coronafs coronafs -" "d ${toString cfg.dataDir}/metadata 0750 coronafs coronafs -" "d ${toString cfg.dataDir}/pids 0750 coronafs coronafs -" ]; }; }