This commit is contained in:
atagen 2025-07-19 14:34:21 +10:00
commit e7fa12d6f3
6 changed files with 688 additions and 0 deletions

258
flake.nix Normal file
View file

@ -0,0 +1,258 @@
{
# inputs = {
# nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
# unf = {
# url = "git+https://git.atagen.co/atagen/unf";
# inputs.nixpkgs.follows = "nixpkgs";
# };
# };
outputs =
{
self,
# nixpkgs,
# unf,
}:
{
# uncomment for doc gen
# packages.x86_64-linux.docs =
# let
# pkgs = nixpkgs.legacyPackages.x86_64-linux;
# in
# pkgs.callPackage unf.lib.pak-chooie {
# inherit self;
# projectName = "arbys";
# newPath = "https://git.atagen.co/atagen/arbys";
# modules = [ self.nixosModules.arbys ];
# };
nixosModules.arbys =
{
pkgs,
lib,
config,
...
}:
let
inherit (lib)
mkEnableOption
mkOption
mkIf
;
inherit (lib.types)
submodule
bool
nullOr
lines
path
int
attrsOf
;
fileType = submodule (
{ options, ... }:
{
options = {
enable = mkEnableOption "creation of this file" // {
default = true;
example = false;
};
text = mkOption {
default = "";
type = lines;
description = "Text of the file";
};
source = mkOption {
type = nullOr path;
default = null;
description = "Path of the source file or directory";
};
executable = mkOption {
type = bool;
default = false;
example = true;
description = ''
Whether to set the execute bit on the target file.
Only effective in combination with `.text` specified, else permissions will be of the source file.
'';
};
uid = mkOption {
type = nullOr int;
default = null;
description = ''
Uid this file will belong to
'';
};
gid = mkOption {
type = nullOr int;
default = null;
description = ''
Gid this file will belong to
'';
};
clobber = mkOption {
default = config.environment.arbys.clobber;
defaultText = ''config.environment.arbys.clobber'';
type = bool;
description = ''
Whether to clobber file or throw error on conflict
'';
};
};
}
);
in
{
options.environment = {
arbys = {
clobber = mkOption {
type = bool;
default = false;
description = ''
Whether to clobber conflicted files by default
'';
};
enable = mkEnableOption "Arbitrary Symlink Manager";
};
files = mkOption {
description = "Files to link";
type = attrsOf fileType;
default = { };
};
};
config =
let
files =
let
enabled = lib.filterAttrs (_: v: v.enable) config.environment.files;
sourced = builtins.mapAttrs (
n: v:
let
processed =
if (v.source == null) then
(
if (v.text == "") then
throw "Must provide source or text for ${n}"
else
v
// {
source = pkgs.writeTextFile {
name = "${n}-source";
text = v.text;
executable = v.executable ? false;
};
}
)
else
v;
filtered = builtins.removeAttrs processed [
"text"
"enable"
"executable"
];
nullFiltered = lib.filterAttrs (_: v: v != null) filtered;
in
nullFiltered
// {
type = "symlink";
}
) enabled;
in
lib.foldlAttrs (
acc: n: v:
acc ++ (lib.singleton ({ target = n; } // v))
) [ ] sourced;
manifest = pkgs.writeTextFile {
name = "manifest.json";
text = (
builtins.toJSON {
inherit files;
clobber_by_default = config.environment.arbys.clobber;
version = 1;
}
);
checkPhase = ''
set -e
CUE_CACHE_DIR=$(pwd)/.cache
CUE_CONFIG_DIR=$(pwd)/.config
${lib.getExe pkgs.cue} vet -c ${./v1.cue} $target
'';
};
in
mkIf (config.environment.arbys.enable) {
systemd.targets.arbys = {
description = "Create Arbitrary Symlinks";
requiredBy = [ "sysinit-reactivation.target" ];
before = [ "sysinit-reactivation.target" ];
requires = [
"arbys-activate.service"
"arbys-copy.service"
"arbys-cleanup.service"
];
};
systemd.services =
let
manifestPath = "/var/lib/arbys";
in
{
arbys-prep = {
description = "Ensure manifest dir exists";
script = "mkdir -p ${manifestPath}";
serviceConfig.Type = "oneshot";
unitConfig.RefuseManualStart = true;
};
arbys-activate = {
description = "Link files from arbys manifest";
serviceConfig.Type = "oneshot";
requires = [
"arbys-prep.service"
"arbys-copy.service"
];
after = [ "arbys-prep.service" ];
script =
let
linker = lib.getExe pkgs.smfh;
in
''
new_manifest=${manifest}
if [ ! -f ${manifestPath}/manifest.json ]; then
${linker} activate $new_manifest
exit 0
fi;
${linker} diff $new_manifest ${manifestPath}/manifest.json
'';
};
arbys-copy = {
description = "Copy manifest into the state dir";
serviceConfig.Type = "oneshot";
after = [ "arbys-activate.service" ];
script = "cp ${manifest} ${manifestPath}/manifest.json";
};
arbys-cleanup = {
description = "Clean up manifest if disabled";
serviceConfig.type = "oneshot";
after = [ "arbys.target" ];
unitConfig.RefuseManualStart = false;
script =
if (lib.count (builtins.attrNames config.environment.files) == 0) then
"rm ${manifestPath}/manifest.json"
else
"true";
};
};
};
};
};
}