1
0
mirror of https://github.com/kmein/niveum synced 2026-03-16 10:11:08 +01:00
This commit is contained in:
2025-12-27 22:22:54 +01:00
parent cb0307e8bf
commit c3db0404b3
139 changed files with 2630 additions and 1976 deletions

View File

@@ -84,7 +84,11 @@ in
Type = "simple";
ExecStart = ''
${lib.getExe cfg.package} \
${lib.optionalString (cfg.contactInstructions != null) ("--contact " + lib.escapeShellArg cfg.contactInstructions)} \
${
lib.optionalString (cfg.contactInstructions != null) (
"--contact " + lib.escapeShellArg cfg.contactInstructions
)
} \
--host ${cfg.host} \
--index ${pkgs.writeText "index.html" cfg.homePageTemplate} \
--listen ${cfg.listenAddress} \

View File

@@ -4,18 +4,20 @@
pkgs,
...
}:
with lib; let
with lib;
let
cfg = config.services.moodle-dl;
json = pkgs.formats.json {};
json = pkgs.formats.json { };
moodle-dl-json = json.generate "moodle-dl.json" cfg.settings;
stateDirectoryDefault = "/var/lib/moodle-dl";
in {
in
{
options = {
services.moodle-dl = {
enable = mkEnableOption "moodle-dl, a Moodle downloader";
settings = mkOption {
default = {};
default = { };
type = json.type;
description = ''
Configuration for moodle-dl. For a full example, see
@@ -69,11 +71,11 @@ in {
group = "moodle-dl";
};
users.groups.moodle-dl = {};
users.groups.moodle-dl = { };
systemd.services.moodle-dl = {
description = "A Moodle downloader that downloads course content";
wants = ["network-online.target"];
wants = [ "network-online.target" ];
serviceConfig = mkMerge [
{
Type = "oneshot";
@@ -83,11 +85,11 @@ in {
ExecStart = "${cfg.package}/bin/moodle-dl ${lib.optionalString cfg.notifyOnly "--without-downloading-files"}";
ExecStartPre = pkgs.writers.writeDash "moodle-dl-config" "${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${toString moodle-dl-json} ${toString cfg.tokensFile} > ${cfg.directory}/config.json";
}
(mkIf (cfg.directory == stateDirectoryDefault) {StateDirectory = "moodle-dl";})
(mkIf (cfg.directory == stateDirectoryDefault) { StateDirectory = "moodle-dl"; })
];
inherit (cfg) startAt;
};
};
meta.maintainers = [maintainers.kmein];
meta.maintainers = [ maintainers.kmein ];
}

View File

@@ -3,65 +3,69 @@
lib,
pkgs,
...
}: {
}:
{
options.services.panoptikon = {
enable = lib.mkEnableOption "Generic command output / website watcher";
watchers = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule (watcher: {
options = {
script = lib.mkOption {
type = lib.types.path;
description = ''
A script whose stdout is to be watched.
'';
example = ''
pkgs.writers.writeDash "github-meta" '''
''${pkgs.curl}/bin/curl -sSL https://api.github.com/meta | ''${pkgs.jq}/bin/jq
'''
'';
type = lib.types.attrsOf (
lib.types.submodule (watcher: {
options = {
script = lib.mkOption {
type = lib.types.path;
description = ''
A script whose stdout is to be watched.
'';
example = ''
pkgs.writers.writeDash "github-meta" '''
''${pkgs.curl}/bin/curl -sSL https://api.github.com/meta | ''${pkgs.jq}/bin/jq
'''
'';
};
frequency = lib.mkOption {
type = lib.types.str;
description = ''
How often to run the script. See systemd.time(7) for more information about the format.
'';
example = "*:0/3";
default = "daily";
};
loadCredential = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
This can be used to pass secrets to the systemd service without adding them to the nix store.
'';
default = [ ];
};
reporters = lib.mkOption {
type = lib.types.listOf lib.types.path;
description = ''
A list of scripts that take the diff (if any) via stdin and report it (e.g. to IRC, Telegram or Prometheus). The name of the watcher will be in the $PANOPTIKON_WATCHER environment variable.
'';
example = ''
[
(pkgs.writers.writeDash "telegram-reporter" '''
''${pkgs.curl}/bin/curl -X POST https://api.telegram.org/bot''${TOKEN}/sendMessage \
-d chat_id=123456 \
-d text="$(cat)"
''')
(pkgs.writers.writeDash "notify" '''
''${pkgs.libnotify}/bin/notify-send "$PANOPTIKON_WATCHER has changed."
''')
]
'';
};
};
frequency = lib.mkOption {
type = lib.types.str;
description = ''
How often to run the script. See systemd.time(7) for more information about the format.
'';
example = "*:0/3";
default = "daily";
};
loadCredential = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = ''
This can be used to pass secrets to the systemd service without adding them to the nix store.
'';
default = [];
};
reporters = lib.mkOption {
type = lib.types.listOf lib.types.path;
description = ''
A list of scripts that take the diff (if any) via stdin and report it (e.g. to IRC, Telegram or Prometheus). The name of the watcher will be in the $PANOPTIKON_WATCHER environment variable.
'';
example = ''
[
(pkgs.writers.writeDash "telegram-reporter" '''
''${pkgs.curl}/bin/curl -X POST https://api.telegram.org/bot''${TOKEN}/sendMessage \
-d chat_id=123456 \
-d text="$(cat)"
''')
(pkgs.writers.writeDash "notify" '''
''${pkgs.libnotify}/bin/notify-send "$PANOPTIKON_WATCHER has changed."
''')
]
'';
};
};
config = {};
}));
config = { };
})
);
};
};
config = let
cfg = config.services.panoptikon;
in
config =
let
cfg = config.services.panoptikon;
in
lib.mkIf cfg.enable {
users.extraUsers.panoptikon = {
isSystemUser = true;
@@ -70,45 +74,50 @@
group = "panoptikon";
};
users.extraGroups.panoptikon = {};
users.extraGroups.panoptikon = { };
systemd.timers = lib.attrsets.mapAttrs' (watcherName: _:
systemd.timers = lib.attrsets.mapAttrs' (
watcherName: _:
lib.nameValuePair "panoptikon-${watcherName}" {
timerConfig.RandomizedDelaySec = toString (60 * 60);
})
cfg.watchers;
}
) cfg.watchers;
systemd.services =
lib.attrsets.mapAttrs' (watcherName: watcherOptions:
lib.nameValuePair "panoptikon-${watcherName}" {
enable = true;
startAt = watcherOptions.frequency;
serviceConfig = {
Type = "oneshot";
User = "panoptikon";
Group = "panoptikon";
WorkingDirectory = "/var/lib/panoptikon";
RestartSec = toString (60 * 60);
Restart = "on-failure";
LoadCredential = watcherOptions.loadCredential;
};
unitConfig = {
StartLimitIntervalSec = "300";
StartLimitBurst = "5";
};
environment.PANOPTIKON_WATCHER = watcherName;
wants = ["network-online.target"];
script = ''
set -fux
${watcherOptions.script} > ${lib.escapeShellArg watcherName}
diff_output=$(${pkgs.diffutils}/bin/diff --new-file ${lib.escapeShellArg (watcherName + ".old")} ${lib.escapeShellArg watcherName} || :)
if [ -n "$diff_output" ]
then
${lib.strings.concatMapStringsSep "\n" (reporter: ''echo "$diff_output" | ${reporter} || :'') watcherOptions.reporters}
fi
mv ${lib.escapeShellArg watcherName} ${lib.escapeShellArg (watcherName + ".old")}
'';
})
cfg.watchers;
systemd.services = lib.attrsets.mapAttrs' (
watcherName: watcherOptions:
lib.nameValuePair "panoptikon-${watcherName}" {
enable = true;
startAt = watcherOptions.frequency;
serviceConfig = {
Type = "oneshot";
User = "panoptikon";
Group = "panoptikon";
WorkingDirectory = "/var/lib/panoptikon";
RestartSec = toString (60 * 60);
Restart = "on-failure";
LoadCredential = watcherOptions.loadCredential;
};
unitConfig = {
StartLimitIntervalSec = "300";
StartLimitBurst = "5";
};
environment.PANOPTIKON_WATCHER = watcherName;
wants = [ "network-online.target" ];
script = ''
set -fux
${watcherOptions.script} > ${lib.escapeShellArg watcherName}
diff_output=$(${pkgs.diffutils}/bin/diff --new-file ${
lib.escapeShellArg (watcherName + ".old")
} ${lib.escapeShellArg watcherName} || :)
if [ -n "$diff_output" ]
then
${lib.strings.concatMapStringsSep "\n" (
reporter: ''echo "$diff_output" | ${reporter} || :''
) watcherOptions.reporters}
fi
mv ${lib.escapeShellArg watcherName} ${lib.escapeShellArg (watcherName + ".old")}
'';
}
) cfg.watchers;
};
}

View File

@@ -3,7 +3,8 @@
lib,
pkgs,
...
}: let
}:
let
cfg = config.niveum.passport;
sortOn = a: lib.sort (as1: as2: lib.lessThan (lib.getAttr a as1) (lib.getAttr a as2));
css = ''
@@ -52,20 +53,22 @@
}
'';
in
with lib; {
options.niveum.passport = {
enable = mkEnableOption "server passport";
with lib;
{
options.niveum.passport = {
enable = mkEnableOption "server passport";
introductionHTML = mkOption {type = types.str;};
introductionHTML = mkOption { type = types.str; };
virtualHost = mkOption {
type = types.str;
};
virtualHost = mkOption {
type = types.str;
};
services = mkOption {
type = types.listOf (types.submodule {
services = mkOption {
type = types.listOf (
types.submodule {
options = {
title = mkOption {type = types.str;};
title = mkOption { type = types.str; };
link = mkOption {
type = types.nullOr types.str;
default = null;
@@ -75,61 +78,62 @@ in
default = "";
};
};
});
default = [];
}
);
default = [ ];
};
};
config = mkIf cfg.enable {
services.nginx.enable = true;
services.nginx.virtualHosts."${cfg.virtualHost}".locations."/passport".extraConfig = ''
default_type "text/html";
root ${
pkgs.linkFarm "www" [
{
name = "passport/index.html";
path = pkgs.writeText "index.html" ''
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${config.networking.hostName} passport</title>
<style>${css}</style>
</head>
<body>
<main>
<section id="server">
<h1>${config.networking.hostName}</h1>
${cfg.introductionHTML}
</section>
<section id="services">
<h2>Services</h2>
<dl>
${lib.strings.concatMapStringsSep "\n" (service: ''
<dt>
${lib.optionalString (service.link != null) "<a href=\"${service.link}\">"}
${service.title}
${lib.optionalString (service.link != null) "</a>"}
</dt>
<dd>
${service.description}
</dd>
'') (sortOn "title" cfg.services)}
</dl>
</section>
</main>
<footer>
<tt>${config.networking.hostName}</tt> is part of the <i><a href="https://github.com/kmein/niveum/tree/master/systems/${config.networking.hostName}">niveum</a></i> network.
</footer>
</body>
'';
}
]
};
};
config = mkIf cfg.enable {
services.nginx.enable = true;
services.nginx.virtualHosts."${cfg.virtualHost}".locations."/passport".extraConfig = ''
default_type "text/html";
root ${
pkgs.linkFarm "www" [
{
name = "passport/index.html";
path = pkgs.writeText "index.html" ''
<!doctype html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${config.networking.hostName} passport</title>
<style>${css}</style>
</head>
<body>
<main>
<section id="server">
<h1>${config.networking.hostName}</h1>
${cfg.introductionHTML}
</section>
<section id="services">
<h2>Services</h2>
<dl>
${lib.strings.concatMapStringsSep "\n" (service: ''
<dt>
${lib.optionalString (service.link != null) "<a href=\"${service.link}\">"}
${service.title}
${lib.optionalString (service.link != null) "</a>"}
</dt>
<dd>
${service.description}
</dd>
'') (sortOn "title" cfg.services)}
</dl>
</section>
</main>
<footer>
<tt>${config.networking.hostName}</tt> is part of the <i><a href="https://github.com/kmein/niveum/tree/master/systems/${config.networking.hostName}">niveum</a></i> network.
</footer>
</body>
'';
}
]
};
index index.html;
'';
};
}
index index.html;
'';
};
}

View File

@@ -4,7 +4,8 @@
pkgs,
...
}:
with lib; let
with lib;
let
cfg = config.services.power-action;
out = {
@@ -27,7 +28,8 @@ with lib; let
default = "*:0/1";
};
plans = mkOption {
type = with types;
type =
with types;
attrsOf (submodule {
options = {
charging = mkOption {
@@ -71,14 +73,18 @@ with lib; let
state="$(${state})"
${concatStringsSep "\n" (mapAttrsToList writeRule cfg.plans)}
'';
charging_check = plan:
if (plan.charging == null)
then ""
else if plan.charging
then ''&& [ "$state" = "true" ]''
else ''&& ! [ "$state" = "true" ]'';
charging_check =
plan:
if (plan.charging == null) then
""
else if plan.charging then
''&& [ "$state" = "true" ]''
else
''&& ! [ "$state" = "true" ]'';
writeRule = _: plan: "if [ $power -ge ${toString plan.lowerLimit} ] && [ $power -le ${toString plan.upperLimit} ] ${charging_check plan}; then ${plan.action}; fi";
writeRule =
_: plan:
"if [ $power -ge ${toString plan.lowerLimit} ] && [ $power -le ${toString plan.upperLimit} ] ${charging_check plan}; then ${plan.action}; fi";
powerlvl = pkgs.writers.writeDash "powerlvl" ''
cat /sys/class/power_supply/${cfg.battery}/capacity
@@ -91,4 +97,4 @@ with lib; let
fi
'';
in
out
out

View File

@@ -4,10 +4,12 @@
lib,
...
}:
with lib; let
with lib;
let
netname = "retiolum";
cfg = config.networking.retiolum;
in {
in
{
options = {
networking.retiolum.ipv4 = mkOption {
type = types.str;
@@ -33,10 +35,9 @@ in {
config = {
services.tinc.networks.${netname} = {
name = cfg.nodename;
hosts =
builtins.mapAttrs
(name: _: builtins.readFile "${<retiolum/hosts>}/${name}")
(builtins.readDir <retiolum/hosts>);
hosts = builtins.mapAttrs (name: _: builtins.readFile "${<retiolum/hosts>}/${name}") (
builtins.readDir <retiolum/hosts>
);
rsaPrivateKeyFile = toString <system-secrets/retiolum.key>;
ed25519PrivateKeyFile = toString <system-secrets/retiolum.ed25519>;
extraConfig = ''
@@ -47,11 +48,11 @@ in {
networking.extraHosts = builtins.readFile (toString <retiolum/etc.hosts>);
environment.systemPackages = [config.services.tinc.networks.${netname}.package];
environment.systemPackages = [ config.services.tinc.networks.${netname}.package ];
networking.firewall = {
allowedTCPPorts = [655];
allowedUDPPorts = [655];
allowedTCPPorts = [ 655 ];
allowedUDPPorts = [ 655 ];
};
#services.netdata.portcheck.checks.tinc.port = 655;

View File

@@ -4,32 +4,35 @@
pkgs,
...
}:
with lib; {
with lib;
{
options.niveum = {
wirelessInterface = mkOption {type = types.str;};
wirelessInterface = mkOption { type = types.str; };
batteryName = mkOption {type = types.str;};
batteryName = mkOption { type = types.str; };
promptColours = let
colours16 = types.enum [
"black"
"red"
"green"
"yellow"
"blue"
"magenta"
"cyan"
"white"
];
in {
success = mkOption {
type = colours16;
default = "green";
promptColours =
let
colours16 = types.enum [
"black"
"red"
"green"
"yellow"
"blue"
"magenta"
"cyan"
"white"
];
in
{
success = mkOption {
type = colours16;
default = "green";
};
failure = mkOption {
type = colours16;
default = "red";
};
};
failure = mkOption {
type = colours16;
default = "red";
};
};
};
}

View File

@@ -4,24 +4,29 @@
pkgs,
...
}:
with lib; let
with lib;
let
cfg = config.niveum.bots;
botService = name: bot:
botService =
name: bot:
nameValuePair "bot-${name}" {
enable = bot.enable;
startAt = bot.time;
serviceConfig = {
Type = "oneshot";
LoadCredential = lib.optionals (bot.telegram.enable) [
"telegram-token:${bot.telegram.tokenFile}"
] ++ lib.optionals (bot.mastodon.enable) [
"mastodon-token:${bot.mastodon.tokenFile}"
] ++ lib.optionals (bot.matrix.enable) [
"matrix-token:${bot.matrix.tokenFile}"
];
LoadCredential =
lib.optionals (bot.telegram.enable) [
"telegram-token:${bot.telegram.tokenFile}"
]
++ lib.optionals (bot.mastodon.enable) [
"mastodon-token:${bot.mastodon.tokenFile}"
]
++ lib.optionals (bot.matrix.enable) [
"matrix-token:${bot.matrix.tokenFile}"
];
};
wants = ["network-online.target"];
wants = [ "network-online.target" ];
script = ''
QUOTE=$(${bot.command})
if [ -n "$QUOTE" ]; then
@@ -30,12 +35,14 @@ with lib; let
${lib.optionalString (bot.matrix.enable) ''
export MATRIX_TOKEN="$(cat "$CREDENTIALS_DIRECTORY/matrix-token")"
export JSON_PAYLOAD=$(${pkgs.jq}/bin/jq -n --arg msgtype "m.text" --arg body "$QUOTE" '{msgtype: $msgtype, body: $body}')
${strings.concatStringsSep "\n" (map (chatId: ''
${pkgs.curl}/bin/curl -X POST "https://${bot.matrix.homeserver}/_matrix/client/r0/rooms/${chatId}/send/m.room.message" \
-d "$JSON_PAYLOAD" \
-H "Authorization: Bearer $MATRIX_TOKEN" \
-H "Content-Type: application/json"
'') bot.matrix.chatIds)}
${strings.concatStringsSep "\n" (
map (chatId: ''
${pkgs.curl}/bin/curl -X POST "https://${bot.matrix.homeserver}/_matrix/client/r0/rooms/${chatId}/send/m.room.message" \
-d "$JSON_PAYLOAD" \
-H "Authorization: Bearer $MATRIX_TOKEN" \
-H "Content-Type: application/json"
'') bot.matrix.chatIds
)}
''}
${lib.optionalString (bot.mastodon.enable) ''
@@ -49,78 +56,90 @@ with lib; let
${lib.optionalString (bot.telegram.enable) ''
export TELEGRAM_TOKEN="$(cat "$CREDENTIALS_DIRECTORY/telegram-token")"
${strings.concatStringsSep "\n" (map (chatId: ''
${pkgs.curl}/bin/curl -X POST "https://api.telegram.org/bot''${TELEGRAM_TOKEN}/sendMessage" \
-d chat_id="${chatId}" \
-d text="$QUOTE" ${
lib.strings.optionalString (bot.telegram.parseMode != null)
"-d parse_mode=${bot.telegram.parseMode}"
} | ${pkgs.jq}/bin/jq -e .ok
'')
bot.telegram.chatIds)}
${strings.concatStringsSep "\n" (
map (chatId: ''
${pkgs.curl}/bin/curl -X POST "https://api.telegram.org/bot''${TELEGRAM_TOKEN}/sendMessage" \
-d chat_id="${chatId}" \
-d text="$QUOTE" ${
lib.strings.optionalString (
bot.telegram.parseMode != null
) "-d parse_mode=${bot.telegram.parseMode}"
} | ${pkgs.jq}/bin/jq -e .ok
'') bot.telegram.chatIds
)}
''}
fi
'';
};
in {
in
{
options.niveum.bots = mkOption {
type = types.attrsOf (types.submodule {
options = {
enable = mkEnableOption "Mastodon and Telegram bot";
time = mkOption {type = types.str;};
command = mkOption {type = types.str;};
matrix = mkOption {
default = {};
type = types.submodule {
options = {
enable = mkEnableOption "Posting to Matrix";
tokenFile = mkOption {type = types.path;};
homeserver = mkOption {
type = types.str;
type = types.attrsOf (
types.submodule {
options = {
enable = mkEnableOption "Mastodon and Telegram bot";
time = mkOption { type = types.str; };
command = mkOption { type = types.str; };
matrix = mkOption {
default = { };
type = types.submodule {
options = {
enable = mkEnableOption "Posting to Matrix";
tokenFile = mkOption { type = types.path; };
homeserver = mkOption {
type = types.str;
};
chatIds = mkOption {
type = types.listOf types.str;
};
};
chatIds = mkOption {
type = types.listOf types.str;
};
};
mastodon = mkOption {
default = { };
type = types.submodule {
options = {
enable = mkEnableOption "Posting to Mastodon";
language = mkOption {
type = types.str;
default = "en";
};
tokenFile = mkOption { type = types.path; };
homeserver = mkOption {
type = types.str;
default = "social.krebsco.de";
};
};
};
};
telegram = mkOption {
default = { };
type = types.submodule {
options = {
enable = mkEnableOption "Posting to Telegram";
tokenFile = mkOption { type = types.path; };
chatIds = mkOption {
type = types.listOf (types.strMatching "-?[0-9]+|@[A-Za-z0-9]+");
};
parseMode = mkOption {
type = types.nullOr (
types.enum [
"HTML"
"Markdown"
]
);
default = null;
};
};
};
};
};
mastodon = mkOption {
default = {};
type = types.submodule {
options = {
enable = mkEnableOption "Posting to Mastodon";
language = mkOption {
type = types.str;
default = "en";
};
tokenFile = mkOption {type = types.path;};
homeserver = mkOption {
type = types.str;
default = "social.krebsco.de";
};
};
};
};
telegram = mkOption {
default = {};
type = types.submodule {
options = {
enable = mkEnableOption "Posting to Telegram";
tokenFile = mkOption {type = types.path;};
chatIds = mkOption {
type = types.listOf (types.strMatching "-?[0-9]+|@[A-Za-z0-9]+");
};
parseMode = mkOption {
type = types.nullOr (types.enum ["HTML" "Markdown"]);
default = null;
};
};
};
};
};
});
default = {};
}
);
default = { };
};
config = {systemd.services = attrsets.mapAttrs' botService cfg;};
config = {
systemd.services = attrsets.mapAttrs' botService cfg;
};
}