feat: add NixOS module
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/claude-agent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -8,6 +8,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils, git-hooks }:
|
outputs = { self, nixpkgs, flake-utils, git-hooks }:
|
||||||
|
{
|
||||||
|
nixosModules = {
|
||||||
|
musicfs = import ./nix/module.nix;
|
||||||
|
default = self.nixosModules.musicfs;
|
||||||
|
};
|
||||||
|
} //
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs {
|
pkgs = import nixpkgs {
|
||||||
@@ -31,15 +37,15 @@
|
|||||||
clippy = pkgs.clippy;
|
clippy = pkgs.clippy;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
embedme = {
|
# embedme = {
|
||||||
enable = true;
|
# enable = true;
|
||||||
name = "embedme";
|
# name = "embedme";
|
||||||
description = "Keep README code blocks in sync with source files";
|
# description = "Keep README code blocks in sync with source files";
|
||||||
entry = "${pkgs.nodePackages.embedme}/bin/embedme";
|
# entry = "${pkgs.embedme}/bin/embedme";
|
||||||
args = [ "README.md" ];
|
# args = [ "README.md" ];
|
||||||
pass_filenames = false;
|
# pass_filenames = false;
|
||||||
language = "system";
|
# language = "system";
|
||||||
};
|
# };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
@@ -83,7 +89,7 @@
|
|||||||
protobuf
|
protobuf
|
||||||
grpcurl
|
grpcurl
|
||||||
|
|
||||||
nodePackages.embedme
|
# embedme
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
+220
@@ -0,0 +1,220 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.musicfs;
|
||||||
|
toml = pkgs.formats.toml { };
|
||||||
|
|
||||||
|
originSubmodule = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
id = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Unique identifier for this origin.";
|
||||||
|
};
|
||||||
|
|
||||||
|
originType = lib.mkOption {
|
||||||
|
type = lib.types.enum [ "local" "nfs" "smb" ];
|
||||||
|
default = "local";
|
||||||
|
description = "Storage backend type.";
|
||||||
|
};
|
||||||
|
|
||||||
|
priority = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 1;
|
||||||
|
description = "Failover priority (lower = preferred).";
|
||||||
|
};
|
||||||
|
|
||||||
|
enabled = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether this origin is active.";
|
||||||
|
};
|
||||||
|
|
||||||
|
path = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Filesystem path to the music directory.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraSettings = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf toml.type;
|
||||||
|
default = { };
|
||||||
|
description = "Additional origin-specific settings passed to the config.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mkOriginConfig = o:
|
||||||
|
{
|
||||||
|
id = o.id;
|
||||||
|
origin_type = o.originType;
|
||||||
|
priority = o.priority;
|
||||||
|
enabled = o.enabled;
|
||||||
|
path = o.path;
|
||||||
|
}
|
||||||
|
// o.extraSettings;
|
||||||
|
|
||||||
|
configFile = toml.generate "musicfs.toml" (
|
||||||
|
{
|
||||||
|
mount_point = cfg.mountPoint;
|
||||||
|
cache_dir = cfg.cacheDir;
|
||||||
|
origins = map mkOriginConfig cfg.origins;
|
||||||
|
}
|
||||||
|
// lib.optionalAttrs (cfg.cache != { }) { cache = cfg.cache; }
|
||||||
|
// lib.optionalAttrs (cfg.health != { }) { health = cfg.health; }
|
||||||
|
// lib.optionalAttrs (cfg.logging != { }) { logging = cfg.logging; }
|
||||||
|
// cfg.extraConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
inherit (builtins) map;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.musicfs = {
|
||||||
|
enable = lib.mkEnableOption "MusicFS virtual FUSE filesystem";
|
||||||
|
|
||||||
|
package = lib.mkPackageOption pkgs "musicfs" {
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
mountPoint = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/mnt/music";
|
||||||
|
description = "Where to mount the virtual filesystem.";
|
||||||
|
};
|
||||||
|
|
||||||
|
cacheDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/cache/musicfs";
|
||||||
|
description = "Directory for cache data (CAS chunks, metadata, search index).";
|
||||||
|
};
|
||||||
|
|
||||||
|
grpcPort = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 50052;
|
||||||
|
description = "Port for the gRPC control API.";
|
||||||
|
};
|
||||||
|
|
||||||
|
origins = lib.mkOption {
|
||||||
|
type = lib.types.listOf originSubmodule;
|
||||||
|
default = [ ];
|
||||||
|
description = "Music storage origins.";
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id = "local-music";
|
||||||
|
originType = "local";
|
||||||
|
path = "/srv/music";
|
||||||
|
}
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
cache = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf toml.type;
|
||||||
|
default = { };
|
||||||
|
description = "Cache settings passed directly to [cache] in config.";
|
||||||
|
example = {
|
||||||
|
metadata_cache_mb = 100;
|
||||||
|
content_cache_gb = 10;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
health = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf toml.type;
|
||||||
|
default = { };
|
||||||
|
description = "Health monitoring settings passed directly to [health] in config.";
|
||||||
|
};
|
||||||
|
|
||||||
|
logging = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf toml.type;
|
||||||
|
default = { };
|
||||||
|
description = "Logging settings passed directly to [logging] in config.";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf toml.type;
|
||||||
|
default = { };
|
||||||
|
description = "Additional top-level config keys merged into the generated TOML.";
|
||||||
|
};
|
||||||
|
|
||||||
|
user = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "musicfs";
|
||||||
|
description = "User account under which musicfs runs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
group = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "musicfs";
|
||||||
|
description = "Group under which musicfs runs.";
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether to open the gRPC port in the firewall.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.origins != [ ];
|
||||||
|
message = "services.musicfs.origins must have at least one entry.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
users.users.${cfg.user} = lib.mkIf (cfg.user == "musicfs") {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = cfg.group;
|
||||||
|
home = cfg.cacheDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
users.groups.${cfg.group} = lib.mkIf (cfg.group == "musicfs") { };
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${cfg.mountPoint} 0755 ${cfg.user} ${cfg.group} -"
|
||||||
|
"d ${cfg.cacheDir} 0750 ${cfg.user} ${cfg.group} -"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.musicfs = {
|
||||||
|
description = "MusicFS - Virtual FUSE Filesystem for Music";
|
||||||
|
after = [ "network.target" "local-fs.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "notify";
|
||||||
|
ExecStart = "${lib.getExe cfg.package} mount --config ${configFile} --grpc-port ${toString cfg.grpcPort}";
|
||||||
|
ExecStopPost = "${pkgs.fuse3}/bin/fusermount3 -u ${cfg.mountPoint}";
|
||||||
|
|
||||||
|
User = cfg.user;
|
||||||
|
Group = cfg.group;
|
||||||
|
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
|
||||||
|
# Hardening
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ReadWritePaths = [
|
||||||
|
cfg.mountPoint
|
||||||
|
cfg.cacheDir
|
||||||
|
] ++ map (o: o.path) (builtins.filter (o: o.enabled) cfg.origins);
|
||||||
|
PrivateTmp = true;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
ProtectHome = "read-only";
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
|
||||||
|
# FUSE needs /dev/fuse
|
||||||
|
DeviceAllow = [ "/dev/fuse rw" ];
|
||||||
|
SupplementaryGroups = [ "fuse" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.grpcPort ];
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user