dendrify: comfy-station

This commit is contained in:
2026-03-27 17:49:01 +01:00
parent 5ca75f28db
commit 88b3ff784a
205 changed files with 4036 additions and 1227 deletions

View File

@@ -0,0 +1,61 @@
{
lib,
config,
pkgs,
...
}: let
cfg = config.hive.borg-server;
in {
options.hive.borg-server = {
enable = lib.mkEnableOption "Enable the borg server";
package = lib.mkOption {
type = lib.types.package;
default = pkgs.borgbackup;
example = "pkgs.borgbackup";
description = "The borg package to use";
};
borg_user = lib.mkOption {
type = lib.types.str;
example = "borg";
default = "borg";
description = "The user for the borg repository home.";
};
repositories_path = lib.mkOption {
type = lib.types.path;
example = "/var/lib/borg-repositories";
default = "/var/lib/borg-repositories";
description = "The user for the borg repository home.";
};
repositories = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule {
options = {
name = lib.mkOption {
type = lib.types.nullOr (lib.types.strMatching "^[a-zA-Z0-9._-]+$");
default = null;
example = "borg-repo";
description = "The name of the borg repository. If null, use key of attrset";
};
ssh_public_key = lib.mkOption {
type = lib.types.singleLineStr;
example = "ssh-rsa AAAA...";
description = "The path to the public key for the borg repository.";
};
};
});
};
};
config = lib.mkIf cfg.enable {
users.users.${cfg.borg_user} = {
isNormalUser = true;
description = "Borg user";
home = cfg.repositories_path;
createHome = true;
extraGroups = ["borg"];
openssh.authorizedKeys.keys =
lib.attrsets.mapAttrsToList
(key: repo: "command=\"${cfg.package}/bin/borg serve --restrict-to-path=${cfg.repositories_path}/${lib.defaultTo key repo.name}\",restrict ${repo.ssh_public_key}")
cfg.repositories;
};
};
}

View File

@@ -0,0 +1,123 @@
{
config,
lib,
...
}: let
cfg = config.hive.gitea-instance;
in {
options.hive.gitea-instance = {
enable = lib.mkEnableOption "Enable the Gitea instance";
instanceFQDN = lib.mkOption {
type = lib.types.singleLineStr;
example = "git.example.com";
description = "Fully qualified domain name of the Gitea instance";
};
databasePasswordFile = lib.mkOption {
type = lib.types.path;
example = "/etc/gitea-db-pass.txt";
description = "Path to the file containing the Gitea database password";
};
nativeRunner = lib.mkOption {
type = lib.types.bool;
description = "Install a gitea act_runner using the native nix store";
default = false;
};
};
config = lib.mkIf cfg.enable {
# Gitea instance
services.gitea = {
enable = true;
lfs.enable = true;
appName = "Git yourself some Tea!";
database = {
name = "gitea";
type = "postgres";
passwordFile = cfg.databasePasswordFile;
};
settings = {
server.PROTOCOL = "http+unix";
server.ROOT_URL = "https://${cfg.instanceFQDN}/";
server.DOMAIN = cfg.instanceFQDN;
service.DISABLE_REGISTRATION = true;
};
};
# Fallback server with only 403
services.nginx.virtualHosts.${config.networking.domain} = lib.mkDefault {
default = true;
locations."/".return = 403;
forceSSL = true;
enableACME = true;
};
# Virtual host for gitea
services.nginx.virtualHosts."${cfg.instanceFQDN}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://unix:/run/gitea/gitea.sock";
};
};
# Database setup
services.postgresql = {
enable = true;
ensureDatabases = [config.services.gitea.user];
ensureUsers = [
{
name = config.services.gitea.database.user;
ensureDBOwnership = true;
}
];
};
# act_runner
services.gitea-actions-runner = lib.mkIf cfg.nativeRunner {
instances.nixos-host = {
enable = true;
name = "nixos-host-runner";
url = "https://${cfg.instanceFQDN}";
tokenFile = "/var/lib/gitea-registration/nixos-host";
labels = ["nixos:host"];
settings = {
runner = {
capacity = 1;
};
};
};
};
systemd.services.gitea-runner-nixos-host = lib.mkIf cfg.nativeRunner {
after = ["gitea-runner-gen-token.service"];
requires = ["gitea-runner-gen-token.service"];
serviceConfig.Environment = ''
PATH=/run/current-system/sw/bin:/usr/bin:/bin
'';
};
systemd.services.gitea-runner-gen-token = lib.mkIf cfg.nativeRunner {
wantedBy = ["multi-user.target"];
after = ["gitea.service"];
environment = {
GITEA_CUSTOM = "/var/lib/gitea/custom";
GITEA_WORK_DIR = "/var/lib/gitea";
};
script = ''
set -euo pipefail
token=$(${config.services.gitea.package}/bin/gitea actions generate-runner-token)
echo "TOKEN=$token" > /var/lib/gitea-registration/nixos-host
'';
unitConfig.ConditionPathExists = ["!/var/lib/gitea-registration/nixos-host"];
serviceConfig = {
User = "gitea";
Group = "gitea";
StateDirectory = "gitea-registration";
Type = "oneshot";
RemainAfterExit = true;
};
};
};
}

View File

@@ -0,0 +1,131 @@
{
config,
lib,
...
}: let
cfg = config.hive.gotify-instance;
server-config = {
server = {
listenaddr = "localhost";
port = 54545;
ssl.enabled = false;
ssl.redirecttohttps = false;
cors.alloworigins = ["${cfg.instanceFQDN}"];
stream.allowedorigins = ["${cfg.instanceFQDN}"];
};
database = {
dialect = "postgres";
connection = "host=/run/postgresql dbname=${cfg.user} user=${cfg.user}";
};
defaultuser = {
name = "admin";
pass = config.sops.placeholder.${cfg.adminPasswordSopsKey};
};
registration = false;
};
server-config-yaml = lib.generators.toYAML {} server-config;
in {
options.hive.gotify-instance = {
enable = lib.mkEnableOption "Enable the Gotify instance";
instanceFQDN = lib.mkOption {
type = lib.types.singleLineStr;
example = "gotify.example.com";
description = "Fully qualified domain name of the Gotify instance";
};
user = lib.mkOption {
type = lib.types.singleLineStr;
default = "gotify";
description = "The user to run the service as";
};
group = lib.mkOption {
type = lib.types.singleLineStr;
default = "gotify";
description = "The group to run the service as";
};
adminPasswordSopsKey = lib.mkOption {
type = lib.types.singleLineStr;
description = "The SOPS key for the default admin user";
};
};
config = lib.mkIf cfg.enable {
services.gotify.enable = true;
# Config setup
sops.templates."gotify-server-config.yml" = {
owner = cfg.user;
content = server-config-yaml;
};
environment.etc."gotify/config.yml".source = config.sops.templates."gotify-server-config.yml".path;
# User setup
users.users = lib.mkIf (cfg.user == "gotify") {
gotify = {
description = "Gotify service";
useDefaultShell = true;
group = cfg.group;
isSystemUser = true;
};
};
users.groups = lib.mkIf (cfg.group == "gotify") {
gotify = {};
};
# Configure gotify to run as the specified user (for postgres authentication)
systemd.services.gotify-server = {
serviceConfig = {
DynamicUser = lib.mkForce false;
User = cfg.user;
RuntimeDirectory = "gotify";
};
};
# Fallback server with only 403
services.nginx.virtualHosts.${config.networking.domain} = lib.mkDefault {
default = true;
locations."/".return = 403;
forceSSL = true;
enableACME = true;
};
# Virtual host for gotify
services.nginx.virtualHosts."${cfg.instanceFQDN}" = {
forceSSL = true;
enableACME = true;
locations."/" = {
proxyPass = "http://${server-config.server.listenaddr}:${toString server-config.server.port}";
extraConfig = ''
# Ensuring it can use websockets
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_redirect http:// $scheme://;
# The proxy must preserve the host because gotify verifies the host with the origin
# for WebSocket connections
proxy_set_header Host $host;
# These sets the timeout so that the websocket can stay alive
proxy_connect_timeout 1m;
proxy_send_timeout 1m;
proxy_read_timeout 1m;
'';
};
};
# Database setup
services.postgresql = {
enable = true;
ensureDatabases = [cfg.user];
ensureUsers = [
{
name = cfg.user;
ensureDBOwnership = true;
}
];
};
};
}

View File

@@ -0,0 +1,17 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.hive.services.kdeconnect;
in {
options.hive.services.kdeconnect = {
enable = lib.mkEnableOption "KDE Connect system service (plasma6)";
};
config = lib.mkIf cfg.enable {
programs.kdeconnect.enable = true;
programs.kdeconnect.package = lib.mkForce pkgs.kdePackages.kdeconnect-kde;
};
}

View File

@@ -0,0 +1,44 @@
{
config,
pkgs,
lib,
...
}: let
cfg = config.hive.minecraft-server;
modpack = pkgs.callPackage ./loadCurseForge.nix {
url = "https://mediafilez.forgecdn.net/files/7765/203/BMC3_Server_Pack_v44.zip";
hash = "sha256-doqPzo9fhYM9ng/3RO6OPqhKA2ibfoaKx3Es7M0lpuU=";
};
mcVersion = modpack.variables.MINECRAFT_VERSION;
loader = lib.toLower modpack.variables.MODLOADER;
loaderVersion = modpack.variables.MODLOADER_VERSION;
serverVersion = lib.replaceStrings ["."] ["_"] "${loader}-${mcVersion}";
in {
options.hive.minecraft-server = {
enable = lib.mkEnableOption "Enable BMC3 server";
};
config = lib.mkIf cfg.enable {
services.minecraft-servers = {
enable = true;
eula = true;
openFirewall = true;
servers.bmc3 = lib.optionalAttrs cfg.enable {
enable = true;
autoStart = false;
package = pkgs."${loader}Servers".${serverVersion}.override {inherit loaderVersion;};
jvmOpts = modpack.variables.JAVA_ARGS;
symlinks = {
"mods" = "${modpack.root}/mods";
};
serverProperties = {
motd = "Woher kommt der Stein?";
white-list = true;
};
};
};
};
}

View File

@@ -0,0 +1,36 @@
{
url ? null,
hash ? null,
pkgs,
lib,
...
}: let
modpack = pkgs.fetchzip {
inherit url hash;
stripRoot = false;
};
variablesStr = builtins.readFile "${modpack}/variables.txt";
variableLines =
builtins.filter (l: l != "" && (lib.strings.elemAt (lib.stringToCharacters l) 0) != "#")
(lib.strings.splitString "\n" variablesStr);
stripQuotes = s:
if builtins.match "^\".*\"$" s != null
then builtins.substring 1 (builtins.stringLength s - 2) s
else s;
parseLine = line: let
parts = lib.strings.splitString "=" line;
in {
name = builtins.elemAt parts 0;
value = stripQuotes (builtins.concatStringsSep "=" (builtins.tail parts));
};
variables = builtins.listToAttrs (map parseLine variableLines);
in {
inherit variables;
root = modpack;
mods = "${modpack}/mods";
config = "${modpack}/config";
}

View File

@@ -0,0 +1,134 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.hive.nextcloud-instance;
in {
options.hive.nextcloud-instance = {
enable = lib.mkEnableOption "Enable the Nextcloud instance";
instanceFQDN = lib.mkOption {
type = lib.types.str;
example = "nextcloud.example.com";
description = "Fully qualified domain name of the Nextcloud instance";
};
ssl = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Use SSL and auto-update certificates";
};
adminPasswordFile = lib.mkOption {
type = lib.types.path;
example = "/etc/nc-admin-pass.txt";
description = "Path to the file containing the Nextcloud admin password";
};
};
config = lib.mkIf cfg.enable {
services.nextcloud = {
# Instance
enable = true;
package = pkgs.nextcloud33;
hostName = cfg.instanceFQDN;
https = cfg.ssl;
configureRedis = true;
# DB
config.dbtype = "pgsql";
config.dbhost = "/run/postgresql";
config.adminpassFile = cfg.adminPasswordFile;
#Mail
settings = {
mail_smtpmode = "sendmail";
mail_sendmailmode = "pipe";
};
# Apps
extraAppsEnable = true;
extraApps = {
inherit
(config.services.nextcloud.package.packages.apps)
calendar
contacts
tasks
;
drop_account = pkgs.fetchNextcloudApp {
sha256 = "sha256-J+bZVNIb/MokuTYQu8RBKdnZFakh180pa1pW5KHlC80=";
url = "https://packages.framasoft.org/projects/nextcloud-apps/drop-account/drop_account-3.0.0.tar.gz";
license = "agpl3Only";
};
};
# Raise Upload limit
maxUploadSize = "20G";
# Preview settings (video may be a security risk)
settings = {
enable_previews = true;
enabledPreviewProviders = [
"OC\\Preview\\BMP"
"OC\\Preview\\GIF"
"OC\\Preview\\JPEG"
"OC\\Preview\\Krita"
"OC\\Preview\\MarkDown"
"OC\\Preview\\MP3"
"OC\\Preview\\OpenDocument"
"OC\\Preview\\PNG"
"OC\\Preview\\TXT"
"OC\\Preview\\XBitmap"
"OC\\Preview\\Movie"
"OC\\Preview\\MP4"
"OC\\Preview\\AVI"
"OC\\Preview\\MKV"
];
preview_ffmpeg_path = "${pkgs.ffmpeg}/bin/ffmpeg";
};
};
# Fallback server with only 403
services.nginx.virtualHosts.${config.networking.domain} = lib.mkDefault {
default = true;
locations."/".return = 403;
forceSSL = cfg.ssl;
enableACME = cfg.ssl;
};
# Webserver setup with optional SSL
services.nginx.virtualHosts.${cfg.instanceFQDN} =
if cfg.ssl
then {
forceSSL = true;
enableACME = true;
}
else {
listen = [
{
addr = "0.0.0.0";
port = 80;
}
];
};
security.acme = lib.mkIf cfg.ssl {
acceptTerms = true;
defaults.email = "jonas.roeger+acme@gmail.com";
};
networking.firewall.allowedTCPPorts = [80] ++ lib.optional cfg.ssl 443;
# DB setup
services.postgresql = {
enable = true;
ensureDatabases = ["nextcloud"];
ensureUsers = [
{
name = "nextcloud";
ensureDBOwnership = true;
}
];
};
};
}