mirror of
https://github.com/nix-community/home-manager.git
synced 2026-10-30 16:10:45 +00:00
561 lines
16 KiB
Nix
561 lines
16 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
|
|
let
|
|
inherit (lib)
|
|
attrsToList
|
|
concatLines
|
|
escapeShellArg
|
|
filterAttrsRecursive
|
|
getExe
|
|
hasSuffix
|
|
hm
|
|
isAttrs
|
|
isString
|
|
literalExpression
|
|
maintainers
|
|
makeBinPath
|
|
mkEnableOption
|
|
mkIf
|
|
mkOption
|
|
mkPackageOption
|
|
optionalAttrs
|
|
optionals
|
|
optionalString
|
|
platforms
|
|
types
|
|
;
|
|
inherit (pkgs)
|
|
coreutils
|
|
formats
|
|
jq
|
|
rsync
|
|
runCommand
|
|
systemd
|
|
writeShellScript
|
|
writeText
|
|
;
|
|
|
|
optionalValue = cond: value: if cond then value else null;
|
|
|
|
filterNullOrEmptyAttrsRecursive = filterAttrsRecursive (
|
|
_: v:
|
|
v != null && v != { } && (if isAttrs v then (filterNullOrEmptyAttrsRecursive v) != { } else true)
|
|
);
|
|
|
|
jsonFormat = formats.json { };
|
|
|
|
cfg = config.services.openlinkhub;
|
|
configDir = "${config.xdg.configHome}/OpenLinkHub";
|
|
in
|
|
|
|
{
|
|
meta.maintainers = with maintainers; [ mikaeladev ];
|
|
|
|
options.services.openlinkhub = {
|
|
enable = mkEnableOption "OpenLinkHub";
|
|
|
|
package = mkPackageOption pkgs "openlinkhub" { };
|
|
|
|
allowGamepad = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
example = true;
|
|
description = ''
|
|
Whether to allow OpenLinkHub's virtual gamepad to be enabled.
|
|
|
|
Setting this to `true` will silence the associated warning, but it will
|
|
not grant the required udev permissions. It is up to you as the user to
|
|
supply safe access.
|
|
'';
|
|
};
|
|
|
|
settings =
|
|
let
|
|
settingsModule = types.submodule {
|
|
freeformType = jsonFormat.type;
|
|
options = {
|
|
# required options #
|
|
|
|
listenAddress = mkOption {
|
|
type = types.str;
|
|
default = "127.0.0.1";
|
|
example = "127.0.0.2";
|
|
description = ''
|
|
Which local address the dashboard should listen from.
|
|
'';
|
|
};
|
|
|
|
listenPort = mkOption {
|
|
type = types.ints.between 1 65535;
|
|
default = 27003;
|
|
example = 27004;
|
|
description = ''
|
|
Which port the dashboard should listen from.
|
|
'';
|
|
};
|
|
|
|
# read-only options #
|
|
|
|
frontend = mkOption {
|
|
type = types.bool;
|
|
readOnly = true;
|
|
description = ''
|
|
Whether to enable the dashboard.
|
|
|
|
This is set by
|
|
{option}`config.services.openlinkhub.dashboard.enable`.
|
|
'';
|
|
};
|
|
|
|
memory = mkOption {
|
|
type = types.bool;
|
|
readOnly = true;
|
|
description = ''
|
|
Whether to enable the memory configuration.
|
|
|
|
This is set by
|
|
{option}`config.services.openlinkhub.memory.enable`.
|
|
'';
|
|
};
|
|
|
|
memorySku = mkOption {
|
|
type = with types; nullOr str;
|
|
readOnly = true;
|
|
description = ''
|
|
The SKU of the device.
|
|
|
|
This is set by
|
|
{option}`config.services.openlinkhub.memory.sku`.
|
|
'';
|
|
};
|
|
|
|
memorySmBus = mkOption {
|
|
type = with types; nullOr str;
|
|
readOnly = true;
|
|
description = ''
|
|
The SMBus controller for the device.
|
|
|
|
This is set by
|
|
{option}`config.services.openlinkhub.memory.smb`.
|
|
'';
|
|
};
|
|
|
|
memoryType = mkOption {
|
|
type = with types; nullOr (ints.between 4 5);
|
|
readOnly = true;
|
|
description = ''
|
|
Which generation the device belongs to.
|
|
|
|
This is set by
|
|
{option}`config.services.openlinkhub.memory.type`.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
in
|
|
mkOption {
|
|
type = settingsModule;
|
|
default = { };
|
|
example = {
|
|
debug = false;
|
|
logLevel = "info";
|
|
metrics = false;
|
|
};
|
|
description = ''
|
|
Configuration to be merged into {file}`config.json`.
|
|
|
|
This provides the default value of
|
|
{option}`config.services.openlinkhub.extraConfigs."config.json"`.
|
|
'';
|
|
};
|
|
|
|
dashboard = {
|
|
enable = mkEnableOption "the dashboard";
|
|
|
|
settings = mkOption {
|
|
inherit (jsonFormat) type;
|
|
default = { };
|
|
example = {
|
|
celsius = true;
|
|
languageCode = "en_US";
|
|
pageTitle = "OpenLinkHub WebUI";
|
|
};
|
|
description = ''
|
|
Dashboard configuration to be merged into {file}`dashboard.json`.
|
|
|
|
This provides the default value of
|
|
{option}`config.services.openlinkhub.extraConfigs."dashboard.json"`.
|
|
'';
|
|
};
|
|
};
|
|
|
|
memory = {
|
|
enable = mkEnableOption "memory configuration";
|
|
|
|
sku = mkOption {
|
|
type = with types; nullOr str;
|
|
default = null;
|
|
example = "CMT64GX5M2B5600Z40";
|
|
description = ''
|
|
The SKU of the device.
|
|
|
|
You can find this by running `sudo dmidecode -t memory` and grepping
|
|
by 'Part Number'.
|
|
|
|
See the OpenLinkHub [docs] for more information.
|
|
|
|
[docs]: https://github.com/jurkovic-nikola/OpenLinkHub/blob/main/docs/memory-configuration.md
|
|
'';
|
|
};
|
|
|
|
smb = mkOption {
|
|
type = with types; nullOr (strMatching "i2c-[[:digit:]]+");
|
|
default = null;
|
|
example = "i2c-0";
|
|
description = ''
|
|
The SMBus controller for the device.
|
|
|
|
You can find this by running `i2cdetect -l` and grepping by 'SMBus',
|
|
with it typically being the first device from the list.
|
|
|
|
See the OpenLinkHub [docs] for more information.
|
|
|
|
[docs]: https://github.com/jurkovic-nikola/OpenLinkHub/blob/main/docs/memory-configuration.md
|
|
'';
|
|
};
|
|
|
|
type = mkOption {
|
|
type = with types; nullOr (ints.between 4 5);
|
|
default = null;
|
|
example = 5;
|
|
description = ''
|
|
Which generation the device belongs to.
|
|
|
|
You can find this by running `sudo dmidecode -t memory` and grepping
|
|
by 'Type: DDR[4-5]'.
|
|
|
|
See the OpenLinkHub [docs] for a full list of supported devices and
|
|
their generations.
|
|
|
|
[docs]: https://github.com/jurkovic-nikola/OpenLinkHub/blob/main/docs/supported-devices.md
|
|
'';
|
|
};
|
|
};
|
|
|
|
extraConfigs =
|
|
let
|
|
jsonOrLines = with types; attrsOf (either jsonFormat.type lines);
|
|
|
|
extraConfigsModule = types.submodule {
|
|
freeformType = jsonOrLines;
|
|
options = {
|
|
"config.json" = mkOption {
|
|
type = jsonOrLines;
|
|
default = cfg.settings;
|
|
defaultText = "config.services.openlinkhub.settings";
|
|
description = ''
|
|
Configuration to be written to {file}`config.json`.
|
|
|
|
Setting this value directly will override all values inherited
|
|
from {option}`config.services.openlinkhub.settings`, including any
|
|
values that `settings` inherited from other options in the module.
|
|
'';
|
|
};
|
|
|
|
"dashboard.json" = mkOption {
|
|
type = types.nullOr jsonOrLines;
|
|
default = with cfg.dashboard; optionalAttrs enable settings;
|
|
defaultText = literalExpression ''
|
|
with config.services.openlinkhub.dashboard;
|
|
lib.optionalAttrs enable settings;
|
|
'';
|
|
description = ''
|
|
Configuration to be written to {file}`dashboard.json`.
|
|
|
|
Setting this value directly will override all values inherited
|
|
from {option}`config.services.openlinkhub.dashboard.settings`.
|
|
'';
|
|
};
|
|
};
|
|
};
|
|
in
|
|
mkOption {
|
|
type = extraConfigsModule;
|
|
apply = filterNullOrEmptyAttrsRecursive;
|
|
default = { };
|
|
example = literalExpression ''
|
|
{
|
|
# merges with existing `i2c0.json`
|
|
"database/profiles/i2c0.json" = {
|
|
Active = true;
|
|
Path = "/run/user/1000/OpenLinkHub/database/profiles/i2c0.json";
|
|
Product = "Memory";
|
|
Serial = "i2c0";
|
|
# ...
|
|
};
|
|
# overwrites existing `display.json`
|
|
"display.json" = '''
|
|
[
|
|
{
|
|
"Index": 1,
|
|
"Name": "card1-DP-1",
|
|
"Width": 2560,
|
|
"Height": 1440,
|
|
"Left": false,
|
|
"Top": false
|
|
}
|
|
]
|
|
''';
|
|
}
|
|
'';
|
|
description = ''
|
|
Additional files to be written to {file}`$XDG_CONFIG_HOME/OpenLinkHub`.
|
|
|
|
Content generated from an attrset will be merged, while other content,
|
|
such as lists and lines, will be written as is, overwriting any previous
|
|
data.
|
|
|
|
Null values and empty attrsets will be recursively filtered out.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
assertions = [
|
|
(hm.assertions.assertPlatform "services.openlinkhub" pkgs platforms.linux)
|
|
]
|
|
++ (
|
|
let
|
|
assertMemoryOptionNotNull = opt: {
|
|
assertion = cfg.memory.${opt} != null;
|
|
message = ''
|
|
The option `services.openlinkhub.memory.${opt}` must not be null when
|
|
`services.openlinkhub.memory.enable` is set to true.
|
|
'';
|
|
};
|
|
in
|
|
optionals (cfg.memory.enable) [
|
|
(assertMemoryOptionNotNull "sku")
|
|
(assertMemoryOptionNotNull "smb")
|
|
(assertMemoryOptionNotNull "type")
|
|
]
|
|
);
|
|
|
|
warnings = optionals (!cfg.allowGamepad && ((cfg.settings or { }).enableGamepad or false)) [
|
|
''
|
|
The option `services.openlinkhub.settings.enableGamepad` requires that
|
|
OpenLinkHub have access to uinput devices, which may compromise system security.
|
|
|
|
The Home Manager-supplied udev rule does not include this permission, and thus
|
|
it is up to you to supply safe access.
|
|
|
|
If provided, you can silence this warning by setting:
|
|
services.openlinkhub.allowGamepad = true;
|
|
''
|
|
];
|
|
|
|
services.openlinkhub.settings = {
|
|
frontend = cfg.dashboard.enable;
|
|
memory = cfg.memory.enable;
|
|
memorySku = optionalValue cfg.memory.enable cfg.memory.sku;
|
|
memorySmBus = optionalValue cfg.memory.enable cfg.memory.smb;
|
|
memoryType = optionalValue cfg.memory.enable cfg.memory.type;
|
|
};
|
|
|
|
systemd.user = {
|
|
services.OpenLinkHub = {
|
|
Unit = {
|
|
After = [ "default.target" ];
|
|
Description = cfg.package.meta.description;
|
|
StartLimitIntervalSec = 60;
|
|
StartLimitBurst = 5;
|
|
};
|
|
Service = {
|
|
ExecStart = getExe cfg.package;
|
|
ExecReload = "${coreutils}/bin/kill -s HUP $MAINPID";
|
|
WorkingDirectory = "%t/OpenLinkHub";
|
|
Restart = "always";
|
|
RestartSec = 5;
|
|
};
|
|
Install = {
|
|
WantedBy = [ "default.target" ];
|
|
};
|
|
};
|
|
|
|
tmpfiles.rules = [
|
|
"D %t/OpenLinkHub - - - - -"
|
|
|
|
"L+ %t/OpenLinkHub/static - - - - ${cfg.package}/opt/OpenLinkHub/static"
|
|
"L+ %t/OpenLinkHub/web - - - - ${cfg.package}/opt/OpenLinkHub/web"
|
|
|
|
"L+ %t/OpenLinkHub/database - - - - ${configDir}/database"
|
|
"L+ %t/OpenLinkHub/config.json - - - - ${configDir}/config.json"
|
|
"L+ %t/OpenLinkHub/dashboard.json - - - - ${configDir}/dashboard.json"
|
|
"L+ %t/OpenLinkHub/display.json - - - - ${configDir}/display.json"
|
|
];
|
|
};
|
|
|
|
home.activation = {
|
|
checkOpenLinkHubSetup =
|
|
let
|
|
udevRuleDir = "/etc/udev/rules.d";
|
|
udevRuleName = "70-openlinkhub.rules";
|
|
|
|
udevRule = runCommand udevRuleName { } ''
|
|
cp ${cfg.package}${udevRuleDir}/99-openlinkhub.rules $out
|
|
|
|
sed -i '/^KERNEL=="uinput"/d' $out
|
|
|
|
substituteInPlace $out --replace-fail \
|
|
'OWNER="openlinkhub"' 'TAG+="uaccess"'
|
|
|
|
${optionalString cfg.memory.enable ''
|
|
echo 'KERNEL=="${cfg.memory.smb}", MODE="0660", TAG+="uaccess"' \
|
|
>> $out
|
|
''}
|
|
'';
|
|
|
|
setupScript = writeShellScript "openlinkhub-setup" ''
|
|
set -euo pipefail
|
|
|
|
PATH="${makeBinPath [ systemd ]}''${PATH:+:}$PATH"
|
|
|
|
echo "Linking udev rule..."
|
|
sudo mkdir -p ${udevRuleDir}
|
|
sudo ln -fs ${udevRule} ${udevRuleDir}/${udevRuleName}
|
|
sudo ln -fs ${udevRuleDir}/${udevRuleName} \
|
|
''${NIX_STATE_DIR:-/nix/var/nix}/gcroots/${udevRuleName}
|
|
|
|
echo "Reloading udev rules..."
|
|
sudo udevadm control --reload-rules
|
|
sudo udevadm trigger
|
|
|
|
echo "Restarting OpenLinkHub..."
|
|
systemctl --user restart OpenLinkHub.service
|
|
|
|
echo "Success!"
|
|
'';
|
|
in
|
|
mkIf (config.targets.genericLinux.enable && (!cfg.memory.enable || cfg.memory.smb != null)) (
|
|
hm.dag.entryAnywhere ''
|
|
if [ ! -L ${udevRuleDir}/${udevRuleName} ]; then
|
|
warnEcho "To finish setting up OpenLinkHub, run"
|
|
warnEcho " ${setupScript}"
|
|
elif ! cmp -s ${udevRuleDir}/${udevRuleName} ${udevRule}; then
|
|
warnEcho "OpenLinkHub requires an update, run"
|
|
warnEcho " ${setupScript}"
|
|
fi
|
|
''
|
|
);
|
|
|
|
configureOpenLinkHub =
|
|
let
|
|
concatMapLines = f: list: concatLines (map f list);
|
|
|
|
withSuffix = suffix: value: if (hasSuffix suffix value) then value else (value + suffix);
|
|
|
|
generatedLines = concatMapLines (
|
|
{ name, value }:
|
|
let
|
|
writerFn = if isString value then writeText else jsonFormat.generate;
|
|
bashFn = if isAttrs value then "mergeConfig" else "writeConfig";
|
|
|
|
configPath = configDir + "/" + (withSuffix ".json" name);
|
|
configFile = writerFn (baseNameOf configPath) value;
|
|
in
|
|
"run ${bashFn} '${configFile}' '${configPath}'"
|
|
) (attrsToList cfg.extraConfigs);
|
|
in
|
|
hm.dag.entryAfter [ "writeBoundary" ] ''
|
|
writeConfig() {
|
|
if [ -e "$2" ]; then
|
|
if ! cmp -s "$1" "$2"; then
|
|
OPENLINKHUB_RELOAD=1
|
|
cat "$1" > "$2"
|
|
fi
|
|
else
|
|
OPENLINKHUB_RELOAD=1
|
|
mkdir -p "$(dirname "$2")"
|
|
cat "$1" > "$2"
|
|
fi
|
|
}
|
|
|
|
mergeConfig() {
|
|
if [ -e "$2" ]; then
|
|
local tempFile
|
|
tempFile=$(mktemp)
|
|
|
|
if ${getExe jq} -sS 'add' "$2" "$1" > "$tempFile" &&
|
|
writeConfig "$tempFile" "$2"; then
|
|
rm -f $tempFile
|
|
else
|
|
rm -f $tempFile
|
|
exit 1
|
|
fi
|
|
else
|
|
writeConfig "$1" "$2"
|
|
fi
|
|
}
|
|
|
|
seedDatabase() {
|
|
local source=${escapeShellArg cfg.package + "/opt/OpenLinkHub/database/"}
|
|
local dest=${escapeShellArg configDir + "/database"}
|
|
|
|
local output
|
|
output="$(${getExe rsync} -ai --chmod=D755,F644 --ignore-existing "$source" "$dest")"
|
|
|
|
if [ -n "$output" ]; then
|
|
OPENLINKHUB_RELOAD=1
|
|
fi
|
|
}
|
|
|
|
run mkdir -p "${configDir}/database"
|
|
|
|
run seedDatabase
|
|
|
|
${generatedLines}
|
|
|
|
unset -f writeConfig mergeConfig seedDatabase
|
|
'';
|
|
|
|
reloadOpenLinkHub =
|
|
let
|
|
systemdCfg = config.systemd.user;
|
|
|
|
ensureSystemd = ''
|
|
env XDG_RUNTIME_DIR="''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
|
|
PATH="${dirOf systemdCfg.systemctlPath}:$PATH" \
|
|
'';
|
|
|
|
systemctl = "${ensureSystemd} systemctl";
|
|
in
|
|
mkIf systemdCfg.enable (
|
|
hm.dag.entryAfter [ "configureOpenLinkHub" "reloadSystemd" ] ''
|
|
if [[ -v OPENLINKHUB_RELOAD ]]; then
|
|
if [[ -v DRY_RUN ]]; then
|
|
echo 'systemctl --user restart OpenLinkHub.service'
|
|
else
|
|
systemdStatus=$(${systemctl} --user is-system-running 2>&1 || true)
|
|
|
|
if [[ $systemdStatus == 'running' || $systemdStatus == 'degraded' ]]; then
|
|
${systemctl} --user restart OpenLinkHub.service
|
|
else
|
|
echo "User systemd daemon not running. Skipping OpenLinkHub reload."
|
|
fi
|
|
|
|
unset -v systemdStatus
|
|
fi
|
|
|
|
unset -v OPENLINKHUB_RELOAD
|
|
fi
|
|
''
|
|
);
|
|
};
|
|
};
|
|
}
|